Problemă rezolvată șir de caractere clasa a XI-a mate-info neintensiv #5

Enunț

Se citește de la tastatură un număr întreg cu maxim 8 cifre. Să se elimine o cifră aleasă astfel încât numărul rămas să aibă cifrele în ordine crescătoare. Dacă sunt mai multe soluții se vor afișa toate, iar dacă problema nu are nici o soluție se va tipări un mesaj.

Exemplu: pentru m=2435, poate fi eliminată cifra 3 rămânând numărul 245, sau cifra 4 rămânând numărul 235.

Rezolvare

#include
#include
using namespace std;

int main()
{
 char s[9];
 int l;

 cout << "Introduceti un numar: ";
 cin >> s;
 l = strlen(s);

 bool existaSolutie = false;
 for (int i = 0; i < l; ++i)
 {
  char s2[9];
  strcpy_s(s2, s);

  for (int j = i + 1; j < l; ++j)
  {
   s2[j – 1] = s2[j];
  }
  s2[l – 1] = ‘\0’;

  bool sc = true;
  for (int j = 1; j < l – 1; ++j)
  {
   if ((s2[j – 1] – ‘0’) > (s2[j] – ‘0’))
   {
    sc = false;
    break;
   }
  }

  if (sc)
  {
   cout << s2 << endl;
   existaSolutie = true;
  }
 }

 if (!existaSolutie)
 {
  cout << "Nu exista solutii." << endl;
 }

 return 0;
}

Vizual

Explicații

strcpy_s este varianta sigură a strcpy în Visual C++, folosită fiindcă strcpy dă eroare de compilare.

Exemple de încercat:

  • 123456
  • 5432

Problemă rezolvată șir de caractere clasa a XI-a mate-info neintensiv #4

Enunț

Se citește de la tastatură un șir alcătuit din cel mult 100 litere mici ale alfabetului englez. Acest șir va fi supus unor procese de eliminare a tuturor secvențelor de caractere identice situate pe poziții alăturate, eliminările reluându-se până când șirul nu mai conține succesiuni de caractere alăturate egale. Realizați un program care afișează șirul rezultat după aplicarea algoritmului de mai sus.

Exemplu: pentru șirul „vacaantaaa” vom obține în ordine: „vacntaaa” și „vacnt”.

Vizual

Exemple de încercat

  • teesst
  • abbc
  • cool
  • rezolvareaa

Rezolvare

#include
#include
using namespace std;


int main()
{
 char s[101];
 cout <> s;


 bool schimbare;
 do
 {
  schimbare = false;
  int inceput = -1;
  int sfarsit = -1;
  bool in = false;


  int l = strlen(s);

  for (int i = 0; i < l – 1; ++i)
  {
   if (s[i] == s[i + 1])
   {
    if (in)
    {
     sfarsit = i + 1;
    }
    else
    {
     in = true;
     inceput = i;
     sfarsit = i + 1;
     schimbare = true;
    }
   }
   else
   {
    if (in)
    {
     in = false;
     break;
    }
   }
  }
  if (schimbare)
  {
   for (int i = sfarsit + 1; i < l; ++i)
   {
    s[i – sfarsit + inceput – 1] = s[i];
   }
   l = l – (sfarsit – inceput + 1);
   s[l] = ‘\0’;


   cout << s << endl;
  }
 } while (schimbare);


 return 0;
}

Tutorial „Interpolarea șirurilor în C#”

Acest tutorial vă arată cum să folosiți interpolarea șirurilor pentru a formata și a include rezultate de expresii într-un șir rezultat. Exemplele presupun că dvs. sunteți familiarizat cu conceptele de bază C# și formatarea tipurilor .NET. Dacă sunteți noi la interpolarea șirurilor sau formatarea tipurilor .NET, verificați tutorialul interactiv de interpolare a șirurilor mai întâi. Pentru mai multe informații despre formatarea tipurilor în .NET, vedeți subiectul Formatting Types in .NET.

Notă. Exemplele C# din acest articol rulează in interpretorul în linie și locul de joacă Try.NET. Selectați butonul Run pentru a rula un exemplu în fereastra interactivă. Odată ce dvs. ați executat codul, puteți să îl modificați și să rulați codul modificat selectând Run din nou. Codul modificat fie rulează în fereastra interactivă sau, dacă compilarea eșuează, fereastra interactivă afișează toate mesajele de eroare ale compilatorului C#.

Introducere

Facilitatea de interpolare a șirurilor este construită peste facilitatea de formatare compusă și furnizează o sintaxă mai lizibilă și mai convenientă pentru a include rezultate de expresii formatate într-un șir rezultat.

Pentru a identifica un șir literal ca un șir interpolat, precedați-l cu simbolul $. Puteți incorpora oricare expresie C# validă care întoarce un rezultat într-un șir interpolat. In exemplul următor, de îndată ce o expresie este evaluată, rezultatul ei este convertit într-un șir și inclus într-un șir rezultat:

double a = 3;
double b = 4;
Console.WriteLine($”Aria triunghiului dreptunghic cu catetele {a} si {b} este {0.5 * a * b}”);
Console.WriteLine($”Lungimea ipotenuzei triunghiului dreptunghic cu catetele {a} si {b} este {CalculateHypotenuse(a, b)}”);


double CalculateHypotenuse(double leg1, double leg2) => Math.Sqrt(leg1 * leg1 + leg2 * leg2);

// Iesire asteptata:
// Aria triunghiului dreptunghic cu catetele 3 si 4 este 6
// Lungimea ipotenuzei triunghiului dreptunghic cu catetele 3 si 4 este 5


După cum arată exemplul, dvs. includeți o expresie într-un șir interpolat închizându-l în acolade:

{}

In momentul compilării, un șir interpolat este de obicei transformat într-un apel de metodă String.Format. Aceasta face toate capabilitățile facilității de compunere formatată a șirurilor disponibile dvs. pentru a le folosi cu șirurile interpolate de asemenea.

Cum să specificați un șir de format pentru o expresie interpolată

Dvs. specificați un șir format care este suportat de tipul de expresie rezultat urmând expresia interpolată cu două puncte („:”) și șirul format:

{<expresieInterpolata>:}

Exemplul următor arată cum să specificați șiruri standard și personalizate de format pentru expresii care produc rezultate dată și timp sau numerice:

var date = new DateTime(1731, 11, 25);
Console.WriteLine($”In {date:dddd, MMMM dd, yyyy} Leonhard Euler a introdus litera e sa insemne {Math.E:F5} intr-o scrisoare catre Christian Goldbach.”);


// Iesire asteptata:
// In Sunday, November 25, 1731 Leonhard Euler a introdus litera e sa insemne 2.71828 intr-o scrisoare catre Christian Goldbach.

Pentru mai multe informații, vedeți secțiunea Format String Component a subiectului Composite Formatting. Acea secțiune furnizează link-uri către subiecte care descriu șiruri format standard și personalizate suportate de tipurile de bază .NET.

Cum să controlați lățimea câmpului și alinierea expresiei formatate interpolate

Dvs. specificați lățimea minimă a câmpului și alinierea rezultatului expresiei formatate urmând expresia interpolată cu o virgulă („,”) și expresia constantă:

{<expresieInterpolata>,}

Dacă valoarea aliniere este pozitivă, rezultatul formatat al expresiei este aliniat la dreapta; dacă e negativă, este aliniat la stânga.

Dacă trebuie să specificați și alinierea și un șir format, începeți cu componenta alinierii:

{<expresieInterpolata>,:}

Următorul exemplu arată cum să specificați alinierea și folosește caractere bară verticală („|”) pentru a delimita câmpuri de text:

const int NameAlignment = -9;
const int ValueAlignment = 7;


double a = 3;
double b = 4;
Console.WriteLine($”Trei medii clasice pitagoreice ale lui {a} si {b}:”);
Console.WriteLine($”|{„Aritmetica”,NameAlignment}|{0.5 * (a + b),ValueAlignment:F3}|”);
Console.WriteLine($”|{„Geometrica”,NameAlignment}|{Math.Sqrt(a * b),ValueAlignment:F3}|”);
Console.WriteLine($”|{„Armonica”,NameAlignment}|{2 / (1 / a + 1 / b),ValueAlignment:F3}|”);


// Iesire asteptata:
// Trei medii clasice pitagoreice ale lui 3 si 4:
// |Aritmetica|  3.500|
// |Geometrica|  3.464|
// |Armonica |  3.429|

După cum arată ieșirea exemplului, dacă lungimea rezultatului expresiei formatat întrece lățimea specificată a câmpului, valoarea aliniere este ignorată.

Pentru mai multe informații, vedeți secțiunea Alignment Component a subiectului Composite Formatting.

Cum să folosiți șiruri de evacuare într-un șir interpolat

Sirurile interpolate suportă toate șirurile de evacuare care pot fi folosite în șiruri literale obișnuite. Pentru mai multe informații, vedeți String escape sequences.

Pentru a interpreta șirurile de evacuare literal, folosiți un șir literal textual (en. verbatim). Un șir interpolat textual începe cu caracterul $ urmat de caracterul @.

Pentru a include o acoladă, „{” sau „}”, în șirul rezultat, folosiți două acolade, „{{” sau „}}”. Pentru mai multe informații vedeți secțiunea Escaping Braces a subiectului Composite Formatting.

Următorul exemplu arată cum să includeți acolade în șirul rezultat și să construiți un șir interpolat textual:

var xs = new int[] { 1, 2, 7, 9 };
var ys = new int[] { 7, 9, 12 };
Console.WriteLine($”Gasiti intersectia multimilor {{{string.Join(„, „,xs)}}} si {{{string.Join(„, „,ys)}}}.”);


var userName = „Jane”;
var stringWithEscapes = $”C:\\Users\\{userName}\\Documents”;
var verbatimInterpolated = $@”C:\Users\{userName}\Documents”;
Console.WriteLine(stringWithEscapes);
Console.WriteLine(verbatimInterpolated);


// Iesire asteptata:
// Gasiti intersectia multimilor {1, 2, 7, 9} si {7, 9, 12}.
// C:\Users\Jane\Documents
// C:\Users\Jane\Documents

Cum să folosiți operatorul condițional ternar ?: într-o expresie interpolată

După cum două puncte („:”) are un înțeles special într-un element cu o expresie interpolată, pentru a folosi un operator condițional într-o expresie, închideți-o în paranteze, după cum arată exemplul următor:

var rand = new Random();
for (int i = 0; i < 7; i++)
{
    Console.WriteLine($”Aruncare cu banul: {(rand.NextDouble() < 0.5 ? "stema" : "ban")}");
}

Cum să creați șir rezultat specific unei culturi cu interpolarea șirurilor

Implicit, un șir interpolat folosește cultura curentă definită de proprietatea CultureInfo.CurrentCulture pentru toate operațiile de formatare. Folosiți conversia implicită a unui șir interpolat la o instanță System.FormattableString și apelați-i metoda ToString(IFormatProvider) pentru a crea un șir rezultat specific unei culturi. Următorul exemplu arată cum să faceți aceasta:

var cultures = new System.Globalization.CultureInfo[]
{
    System.Globalization.CultureInfo.GetCultureInfo(„en-US”),
    System.Globalization.CultureInfo.GetCultureInfo(„en-GB”),
    System.Globalization.CultureInfo.GetCultureInfo(„nl-NL”),
    System.Globalization.CultureInfo.InvariantCulture
};


var date = DateTime.Now;
var number = 31_415_926.536;
FormattableString message = $”{date,20}{number,20:N3}”;
foreach (var culture in cultures)
{
    var cultureSpecificMessage = message.ToString(culture);
    Console.WriteLine($”{culture.Name,-10}{cultureSpecificMessage}”);
}


// Iesire asteptata este ca:
// en-US       5/17/18 3:44:55 PM      31,415,926.536
// en-GB      17/05/2018 15:44:55      31,415,926.536
// nl-NL        17-05-18 15:44:55      31.415.926,536
//            05/17/2018 15:44:55      31,415,926.536


După cum arată exemplul, puteți folosi o singură instanță FormattableString pentru a genera multiple șiruri rezultat pentru culturi variate.

Cum să creați un șir rezultat folosind cultura invariantă

Impreună cu metoda FormattableString.ToString(IFormatProvider), dvs. puteți folosi metoda statică FormattableString.Invariant pentru a rezolva un șir interpolat la un șir rezultat pentru InvariantCulture. Următorul exemplu arată cum să faceți aceasta:

string messageInInvariantCulture = FormattableString.Invariant($”Data si timpul in cultura invarianta: {DateTime.Now}”);
Console.WriteLine(messageInInvariantCulture);


// Iesirea asteptata este ca:
// Data si timpul in cultura invarianta: 05/17/2018 15:46:24

Concluzie

Acest tutorial descrie scenarii obișnuite de utilizare a interpolării șirurilor. Pentru mai multe informații despre interpolarea șirurilor, vedeți subiectul String interpolation. Pentru mai multe informații despre formatarea tipurilor în .NET, vedeți subiectele Formatting Types in .NET și Composite formatting.

Vedeți de asemenea

Tradus din această pagină oficială de documentație Microsoft.

Problemă rezolvată șir de caractere clasa a XI-a mate-info neintensiv #2

Enunț

Se citește de la tastatură un cuvânt și apoi un text de maxim 70 de caractere (scris pe un singur rând). Să se afișeze umărul de apariții ale cuvântului citit în cadrul textului.

Exemplu: dacă se citește textul „E bine e foarte bine” și cuvântul „bine”, programul va afișa valoarea 2.

Vizual

Rezolvare

#include

#include

using namespace std;


int main()
{
 char cuvant[71] = „bine”, rand[501];

 cout << "Randul: "; cin.getline(rand, 500);

 int rezultat = 0;
 char *i = rand;
 while (i = strstr(i, cuvant))
 {
  ++rezultat;
  ++i;
 }

 cout << "Cuvantul \"" << cuvant << "\" apare de " <<
  rezultat << " ori in rand." << endl;

 return 0;
}

Explicații

In trecut, în loc de se folosea .
Se consideră că un cuvânt are maxim 70 de caractere și un rând de text are maxim 500 caractere.
rezultat este variabilă de tip întreg care reține ca un contor numărul de apariții ale cuvântului cuvant în rândul de text rand.
i este un pointer (indicator) către începutul rândului de text din rand și la prima parcurgere a condiției de continuare a while-ului se schimbă în prima apariție a cuvântului în rând, sau în NULL dacă nu există asemenea apariție. La următoarea parcurgere, va fi ori NULL, ori poziția celei de-a doua apariții a cuvântului în rând etc.
strstr(i, cuvant) din condiția de continuare a ciclului while se atribuie lui i. acest apel întoarce poziția următoarei apariții a cuvântului în cuvântul care începe la adresa de memorie i (rand[x] este echivalent cu rand + x fiind vorba de pointeri C).

Problemă rezolvată șir de caractere clasa a XI-a mate-info neintensiv

Enunț

Un cuvânt este palindrom dacă citind literele de la dreapta la stânga obținem același cuvânt (de exemplu, cuvintele „cojoc” și „sas” sunt palindroame). Scrieți un program care verifică dacă un cuvânt citit de la tastatură este palindrom sau nu, afișând un mesaj.

Rezolvare

#include
#include
using namespace std;


int main()
{
 char s[201];
 int l;


 cout <> s;

 l = strlen(s);

 bool palindrom = true;

 for (int i = 0; i < l / 2; ++i)
 {
  if (s[i] != s[l – i – 1])
  {
   palindrom = false;
   break;
  }
 }


 cout << (palindrom ? "ESTE PALINDROM." : "NU ESTE PALINDROM.") << endl;

 return 0;
}

Explicații

este antetul pentru scrierea pe ecran și citirea de la tastatură.
(în trecut ) este antetul pentru folosirea funcției strlen.

Presupunem că un cuvânt are maxim 200 de caractere . Al 201-lea este caracterul nul (codul ASCII 0 sau caracterul ‘\0’).

Cu palindrom = true presupunem de la început că cuvântul dat este palindrom și apoi încercăm să demonstrăm contrariul.


Trecem prin fiecare caracter al cuvântului de la stânga la dreapta până la jumătatea lui. Dacă al i-lea caracter de la începutul cuvântului este diferit de al i-lea caracter de la sfârșitul cuvântului, presupunerea e falsă și oprim parcurgerea cu o instrucțiune break.

La sfârșit, înainte de a ieși din main cu return 0, folosim operatorul ?: pentru a afișa un șir de caractere literal în funcție de valoarea variabilei palindrom.

Exemple

Palindromul TOT (3 caractere) : i trece de la 0 la 3 / 2 = 1. Se compară T cu T și O cu O.

Palindromul ABCCBA (6 caractere): i trece de la 0 la 6 / 2 = 3.
Se compară caracterele de pe pozițiile 0 cu 5, 1 cu 4, 2 cu 3. Mai departe nu, deoarece condiția for are semnul strict mai mic, nu mai mic sau egal cu.

[C#] Tutorial „Lucrul cu LINQ”

Tradus din această pagină oficială de documentație Microsoft.

Introducere

Acest tutorial vă învață câteva facilități din .NET Core și limbajul C#. Dvs. veți învăța:
  • Cum să generați secvențe cu LINQ
  • Cum să scrieți metode care pot fi ușor folosite în interogări LINQ.
  • Cum să deosebiți între evaluarea imediată și evaluarea leneșă.
Veți învăța aceste tehnici construind o aplicație care demonstrează una dintre abilitățile de bază ale oricărui magician: faro shuffle. Pe scurt, un faro shuffle este o tehnică în care dvs. împărțiți un pachet de cărți exact în jumătate, apoi amestecul îmbină fiecare singură carte din fiecare jumătate să reconstruiască pachetul inițial.
Magicienii folosesc această tehnică deoarece fiecare carte este într-un loc cunoscut după fiecare amestec, și ordinea se repetă.
Pentru scopurile dvs., ea este o privire superficială la manipularea secvențelor de date. Aplicația pe care o veți construi va construi un pachet de cărți, și apoi va face o serie de amestecuri, afișând secvența de fiecare dată. Dvs. de asemenea veți compara ordinea actualizată cu ordinea originală.

Acest tutorial are pași multipli. După fiecare pas, dvs. puteți rula aplicația și vedea progresul. Dvs. puteți de asemenea vedea exemplul completat în depozitul GitHub dotnet/samples. Pentru instrucțiuni de descărcare, vedeți Samples and Tutorials.

Cerințe preliminare

Dvs. va trebui să vă configurați mașina să ruleze .NET core. Puteți găsi instrucțiunile de instalare pe pagina .NET Core. Puteți rula această aplicație pe Windows, Ubuntu Linux, OS X sau într-un container Docker. Dvs. va trebui să instalați editorul de cod favorit al dvs. Descrierile de mai jos folossc Visual Studio Code care este un editor cu sursă deschisă, cross-platform. Totuși, dvs. puteți folosi oricare unelte cu care sunteți confortabili.

Creați aplicația

Primul pas este să creați o nouă aplicație. Deschideți o linie de comandă și creați un director nou pentru aplicația dvs. Faceți-l directorul curent. Tastați comanda dotnet new console la linia de comandă. Aceasta creează fișierele de începere pentru o aplicație de bază „Hello World”.

Dacă nu ați folosit niciodată C# înainte, acest tutorial explică structura unui program C#. Dvs. puteți învăța acela și apoi să vă întoarceți aici să învățați mai multe despre LINQ.

Crearea setului de date

Inainte de a începe, asigurați-vă că următoarele linii sunt la începutul fișierului Program.cs generat de dotnet new console:

// Program.cs
using System;
using System.Collections.Generic;
using System.Linq;


Dacă aceste trei linii (instrucțiuni using) nu sunt la începutul fișierului, programul nostru nu va compila.

Acum că aveți toate referințele de care aveți nevoie, considerați ce constituie un pachet de cărți. De obicei, un pachet de cărți de joc au patru seturi, și fiecare set are treisprezece valori. In mod normal, dvs. ați considera crearea unei clase Card (Carte) imediat și popularea unei colecții de obiecte Card manual. Cu LINQ, dvs. puteți fi mai concis decât modul obișnuit de a crea un pachet de cărți. In schimbul creării unei clase Card, dvs. puteți crea două secvențe care să reprezinte seturile și respectiv rangurile. Dvs. veți crea o pereche foarte simplă de metode iterator care vor genera rangurile și seturile ca IEnumerable-uri de șiruri:

// Program.cs
// The Main() method


static IEnumerable Suits()
{
    yield return „clubs”;
    yield return „diamonds”;
    yield return „hearts”;
    yield return „spades”;
}


static IEnumerable Ranks()
{
    yield return „two”;
    yield return „three”;
    yield return „four”;
    yield return „five”;
    yield return „six”;
    yield return „seven”;
    yield return „eight”;
    yield return „nine”;
    yield return „ten”;
    yield return „jack”;
    yield return „queen”;
    yield return „king”;
    yield return „ace”;
}


Plasați acestea în interiorul metodei Main în fișierul dvs. Program.cs. Ambele aceste metode utilizează sintaxa yield return pentru a produce secvențe pe măsură ce rulează. Compilatorul construiește un obiect care implementează IEnumerable și generează secvențele de șiruri pe măsură ce sunt cerute.

Acum, utilizați aceste metode iterator pentru a crea pachetul de cărți de joc. Veți plasa interogarea LINQ în metoda noastră Main. Iată o privire la ea:

// Program.cs
static void Main(string[] args)
{
    var startingDeck = from s in Suits()
                       from r in Ranks()
                       select new { Suit = s, Rank = r };


    // Display each card that we’ve generated and placed in startingDeck in the console
    foreach (var card in startingDeck)
    {
        Console.WriteLine(card);
    }
}


Clauzele multiple from produc un SelectMany, care creează o singură secvență din combinarea fiecărui element din prima secvență cu fiecare element din a doua secvență. Ordinea este importantă pentru scopurile noastre. Primul element din prima secvență sursă  (Suits) este combinat cu fiecare lement din secvența a doua (Ranks). Aceasta produce toate cele treisprezece cărți din primul set. Acest proces este repetat cu fiecare element din prima secvență (Suits). Rezultatul final este un pachet de cărți de joc ordonate după seturi, apoi după valori.

Este important să ținem minte că fie că dvs. alegeți să scrieți LINQ-ul dvs. în sintaxa de interogare folosită mai sus sau să folosiți sintaxa metodelor în schimb, este întotdeauna posibil să treceți de la o formă de sintaxă la alta. Interogarea de mai sus scrisă în sintaxă de interogare poate fi scrisă în sintaxa metodelor ca:

var startingDeck = Suits().SelectMany(suit => Ranks(rank => new { Suit = suit, Rank = rank }));

Compilatorul traduce instrucțiunile LINQ scrise cu sintaxă de interogare în sintaxa de apeluri de metode echivalentă. Ca urmare, indiferent de alegerea dvs. de sintaxă, cele două versiuni ale interogării produc același rezultat. Alegeți care sintaxă merge mai bine pentru situația dvs.: de exemplu, dacă dvs. lucrați într-o echipă în care unii din membri au dificultate cu sintaxa metodelor, încercați să preferați folosirea sintaxei de interogare.

Dați-i drumul și rulați exemplul pe care l-ați construit până la acest punct. El va afișa toate cele 52 de cărți din pachet. S-ar putea să găsiți foarte ajutător să rulați acest exemplu sub un depanator să observați cum metodele Suits() și Ranks() se execută. Dvs. puteți să vedeți clar că fiecare șir din fiecare secvență este generat doar când este necesar.

Manipularea ordinii

In continuare, concentrați-vă pe cum veți amesteca aceste cărți din pachet. Primul pas în oricare amestecare bună este să împărțiți pachetul în două. Metodele Take și Skip care sunt parte din API-urile LINQ furnizează această facilitate pentru dvs. Plasați-le sub ciclul foreach:

// Program.cs
public static void Main(string[] args)
{
    var startingDeck = from s in Suits()
                       from r in Ranks()
                       select new { Suit = s, Rank = r };

    foreach (var c in startingDeck)
    {
        Console.WriteLine(c);
    }

    // 52 cards in a deck, so 52 / 2 = 26   
    var top = startingDeck.Take(26);
    var bottom = startingDeck.Skip(26);
}
Cu toate acestea, nu există nici o metodă de amestecare de care să profităm în biblioteca standard, deci dvs. va trebui să vă scrieți propria metodă. Metoda de amestecare pe care o veți fi creând ilustrează câteva tehnici pe care le veți folosi cu programele bazate pe LINQ, deci fiecare parte a acestui proces va fi explicat în pași.
Pentru a adăuga ceva funcționalitate la cum dvs. interacționați cu IEnumerable veți face un pas înapoi de la interogări LINQ, dvs. va trebui să scrieți un fel special de metode numite metode extensii. Pe scurt, o metodă extensie este o metodă statică cu scop special care adaugă funcționalitate nouă la un tip deja existent fără a trebui să modificați tipul original  la care doriți să adăugați funcționalitate.
Dați metodelor extensii ale dvs. o casă nouă adăugând un fișier cu o nouă clasă statică programului dvs. numit Extensions.cs, și apoi începeți să construiți prima metodă extensie:
// Extensions.cs
using System;
using System.Collections.Generic;
using System.Linq;

namespace LinqFaroShuffle
{
    public static class Extensions
    {
        public static IEnumerable InterleaveSequenceWith(this IEnumerable first, IEnumerable second)
        {
            // Your implementation will go here soon enough
        }
    }
}
Priviți pentru un moment semnătura metodei, mai exact parametrii:
public static IEnumerable InterleaveSequenceWith (this IEnumerable first, IEnumerable second)


Dvs. puteți vedea adăugarea modificatorului this pe primul argument către metodă. Aceasta înseamnă că dvs. apelați metoda ca și cum ar fi o metodă membru a tipului primului argument. Această declarație de metodă urmează de asemenea un idiom standard în care tipurile de intrare și ieșire sunt IEnumerable. Această practică dă voie metodelor LINQ să fie înlănțuite împreună pentru a realiza interogări mai complexe.

In mod natural, fiindcă dvs. ați împărțit pachetul în două jumătăți, va trebui să uniți aceste două jumătăți împreună. In cod, aceasta înseamnă că dvs. veți fi enumerând ambele secvențe pe care le-ați obținut prin Take și Skip deodată, intercalând elementele (en. interleaving), și creând o singură secvență: pachetul dvs. acum amestecat de cărți. Scrierea uni metode LINQ care lucrează cu două secvențe necesită să înțelegeți cum funcționează IEnumerable.

Interfața IEnumerable are o singură metodă: GetEnumerator. Obiectul întors de GetEnumerator are o metodă să mute la următorul element, și o proprietate care întoarce elementul curent al secvenței. Dvs. veți folosi acești doi membri pentru a enumera colecția și a întoarce elementele. Această metodă de intercalare va fi o metodă iterator, deci în schimbul construirii unei colecții și întoarcerii colecției, dvs. veți folosi sintaxa yield return arătată mai sus.

Aici este implementarea acelei metode:

public static IEnumerable InterleaveSequenceWith
    (this IEnumerable first, IEnumerable second)
{
    var firstIter = first.GetEnumerator();
    var secondIter = second.GetEnumerator();


    while (firstIter.MoveNext() && secondIter.MoveNext())
    {
        yield return firstIter.Current;
        yield return secondIter.Current;
    }
}


Acum că ați scris această metodă, mergeți înapoi la metoda Main și amestecați pachetul o dată:

// Program.cs
public static void Main(string[] args)
{
    var startingDeck = from s in Suits()
                       from r in Ranks()
                       select new { Suit = s, Rank = r };


    foreach (var c in startingDeck)
    {
        Console.WriteLine(c);
    }

    var top = startingDeck.Take(26);
    var bottom = startingDeck.Skip(26);
    var shuffle = top.InterleaveSequenceWith(bottom);

    foreach (var c in shuffle)
    {
        Console.WriteLine(c);
    }
}

Comparări

Câte amestecuri sunt necesare pentru a pune pachetul în ordinea lui originală? Pentru a afla, dvs. va trebui să scrieți o metodă care determină dacă două secvențe sunt egale. După ce aveți această metodă, dvs. va trebui să plasați codul care amestecă pachetul într-un ciclu, și să verificați când pachetul este înapoi în ordine.

Scrierea unei metode să determinați dacă cele două secvențe sunt egale ar trebui să fie simplu. Este o structură similară cu metoda pe care ați scris-o să amestece pachetul. Dar de această dată, în schimbul yield return-ării fiecărui element, dvs. veți compara elementele potrivite din fiecare secvență. Când întreaga secvență a fost enumerată, dacă fiecare element se potrivește, secvențele sunt aceleași:

Extensions.cs

public static bool SequenceEquals
    (this IEnumerable first, IEnumerable second)
{
    var firstIter = first.GetEnumerator();
    var secondIter = second.GetEnumerator();


    while (firstIter.MoveNext() && secondIter.MoveNext())
    {
        if (!firstIter.Current.Equals(secondIter.Current))
        {
            return false;
        }
    }


    return true;
}


Aceasta arată un al doilea idiom LINQ: metodele terminale. Ele primesc o secvență ca intrare (sau în acest caz, două secvențe), și întorc o singură valoare scalară. Când folosiți metode terminale, ele sunt întotdeauna metoda finală într-un lanț de metode pentru o interogare LINQ, de aici numele „terminal”.

Dvs. puteți vedea acesta în acțiune când dvs. îl folosiți să aflați dacă pachetul este înapoi în ordinea lui originală. Puneți codul de amestecare într-un ciclu, și opriți când secvența este înapoi în ordinea lui originală aplicând metoda SequenceEquals(). Dvs. puteți vedea că ea ar fi întotdeauna metoda finală în oricare interogare, deoarece întoarce o singură valoare în schimbul unei secvențe:

// Program.cs
static void Main(string[] args)
{
    // Query for building the deck


    // Shuffling using InterleaveSequenceWith();

    var times = 0;
    // We can re-use the shuffle variable from earlier, or you can make a new one
    shuffle = startingDeck;
    do
    {
        shuffle = shuffle.Take(26).InterleaveSequenceWith(shuffle.Skip(26));


        foreach (var card in shuffle)
        {
            Console.WriteLine(card);
        }
        Console.WriteLine();
        times++;


    } while (!startingDeck.SequenceEquals(shuffle));

    Console.WriteLine(times);
}


Rulați codul pe care îl aveți până acum și observați cum pachetul se rearanjează la fiecare amestec. După 8 amestecuri (iterații ale ciclului do-while), pachetul se întoarce la configurația originală în care era când l-ați creat prima dată din interogarea LINQ de începere.

Optimizări

Exemplul pe care l-ați construit până acum realizează un out shuffle, în care cărțile de sus și de jos rămân aceleași la fiecare rulare. Haideți să facem o schimbare: vom folosi un in shuffle în schimb, în care toate cele 52 de cărți își schimbă poziția. Pentru un in shuffle, dvs. intercalați pachetul astfel încât prima carte a jumătății de jos devine prima carte din pachet. Aceasta înseamnă că ultima carte din prima jumătate devine cartea de jos. Aceasta este o simplă schimbare la o singură linie de cod. Actualizați interogarea curentă de amestecare inversând pozițiile lui Take și Skip. Aceasta va schimba ordinea jumătății de sus și a jumătății de jos ale pachetului:
shuffle = shuffle.Skip(26).InterleaveSequenceWith(shuffle.Take(26));
Rulați programul din nou, și veți vedea că sunt necesare 52 de iterații pentru ca pachetul să se reordoneze. Dvs. de asemenea veți începe să observați câteva degradări serioase ale performanței pe măsură ce programul continuă să ruleze.
Există câteva motive pentru aceasta. Puteți aborda una dintre cauzele majore ale acestei căderi a performanței: folosirea ineficientă a evaluării leneșe.
Pe scurt, evaluarea leneșă face că evaluarea unei instrucțiuni să nu fie realizată până valoarea ei este necesară. Interogările LINQ sunt instrucțiuni care sunt evaluate leneș. Secvențele sunt generate doar pe măsură ce elementele sunt cerute. In mod obișnuit, acesta este un avantaj major al LINQ. Cu toate acestea, într-o utilizare cum este acest program, aceasta acesta cauzează creștere exponențială în timpul de execuție.
Țineți minte că noi am generat pachetul original folosind o interogare LINQ. Fiecare amestec este generat făcând trei interogări LINQ pe pachetul anterior. Toate acestea sunt realizate leneș. Aceasta înseamnă de asemenea că ele sunt realizate din nou de fiecare dată când secvența este cerută. In momentul în care ajungeți la a 52-a iterație, dvs. veți fi regenerând pachetul original de multe, multe ori. Haideți să scriem un jurnal (en. log) să demonstrăm acest comportament. Apoi, dvs. îl veți corecta.
Iată aici o metodă jurnal care poate fi pusă la sfârșitul oricărei interogări să marcheze că interogarea a fost executată.
public static IEnumerable LogQuery
    (this IEnumerable sequence, string tag)
{
    using (var writer = File.AppendText(„debug.log”))
    {
        writer.WriteLine($”Executing Query {tag}”);
    }

    return sequence;
}

Acum, instrumentați definiția fiecărei interogări cu un mesaj de jurnal:
// Program.cs
public static void Main(string[] args)
{
    var startingDeck = (from s in Suits().LogQuery(„Suit Generation”)
                        from r in Ranks().LogQuery(„Rank Generation”)
                        select new { Suit = s, Rank = r }).LogQuery(„Starting Deck”);

    foreach (var c in startingDeck)
    {
        Console.WriteLine(c);
    }
       

    Console.WriteLine();

    var times = 0;
    var shuffle = startingDeck;


    do
    {
        // Out shuffle
        /*
        shuffle = shuffle.Take(26)
            .LogQuery(„Top Half”)
            .InterleaveSequenceWith(shuffle.Skip(26)
            .LogQuery(„Bottom Half”))
            .LogQuery(„Shuffle”);
        */

        // In shuffle
        shuffle = shuffle.Skip(26)
            .LogQuery(„Bottom Half”)
            .InterleaveSequenceWith(shuffle.Take(26).LogQuery(„Top Half”))
            .LogQuery(„Shuffle”);

        foreach (var c in shuffle)
        {
            Console.WriteLine(c);
        }

        times++;
        Console.WriteLine(times);
    } while (!startingDeck.SequenceEquals(shuffle));

    Console.WriteLine(times);
}
Notați că dvs. nu înregistrați (en. log) de fiecare dată când accesați o interogare. Dvs. înregistrați doar când creați interogarea originală. Programul încă ia mult timp să ruleze, dar acum puteți vedea de ce. Dacă rămâneți fără răbdare rulând in shuffle-ul cu jurnalizarea pornită, treceți înapoi la out shuffle. Dvs. încă veți simți efectele evaluării leneșe. Intr-o rulare, ea execută 2592 de interogări, incluzând toate generările de valoare și set.


Puteți îmbunătăți performanța codului aici pentru a reduce numărul de execuții pe care le faceți. O simplă reparație pe care o puteți face este să cache-uiți rezultatele interogării LINQ originale care construiește pachetul de cărți. In prezent, dvs. executați interogările iar și iar de fiecare dată când ciclul do-while trece printr-o iterație, reconstruind pachetul de cărți și reamestecându-l de fiecare dată. Pentru a cache-ui pachetul de cărți, puteți profita de metodele LINQ ToArray și ToList; când le puneți la sfârșitul interogărilor, ele vor executa aceleași acțiuni pe care le-ați spus să le execute, dar acum ele vor stoca rezultatul într-un tablou (array) sau o listă, depinzând de care metodă alegeți să apelați. Adăugați metoda LINQ ToArray la sfârșitul ambelor interogări și rulați programul din nou:

public static void Main(string[] args)
{
    var startingDeck = (from s in Suits().LogQuery(„Suit Generation”)
                        from r in Ranks().LogQuery(„Value Generation”)
                        select new PlayingCard(s, r))
                        .LogQuery(„Starting Deck”)
                        .ToArray();


    foreach (var c in startingDeck)
    {
        Console.WriteLine(c);
    }


    Console.WriteLine();

    var times = 0;
    var shuffle = startingDeck;


    do
    {
        /*
        shuffle = shuffle.Take(26)
            .LogQuery(„Top Half”)
            .InterleaveSequenceWith(shuffle.Skip(26).LogQuery(„Bottom Half”))
            .LogQuery(„Shuffle”)
            .ToArray();
        */


        shuffle = shuffle.Skip(26)
            .LogQuery(„Bottom Half”)
            .InterleaveSequenceWith(shuffle.Take(26).LogQuery(„Top Half”))
            .LogQuery(„Shuffle”)
            .ToArray();


        foreach (var c in shuffle)
        {
            Console.WriteLine(c);
        }


        times++;
        Console.WriteLine(times);
    } while (!startingDeck.SequenceEquals(shuffle));


    Console.WriteLine(times);
}


Acum amestecul out shuffle este jos la 30 de interogări. Rulați din nou cu in shuffle-ul și veți vedea îmbunătățiri similare: el execută acum 162 de interogări.

Vă rugăm să notați că acest exemplu este proiectat să sublinieze cazurile de utilizare în care evaluarea leneșă poate cauza dificultăți de performanță. In timp ce este important să vedeți unde evaluarea leneșă poate lovi performanța codului, este egal de important să înțelegeți că nu toate interogările ar trebui să ruleze imediat. Lovitura de performanță pe care o întâmpinați fără să folosiți ToArray este deoarece fiecare nou aranjament al pachetului de cărți este construit din aranjamentul anterior. Folosirea evaluării leneșe înseamnă că fiecare nouă configurație a pachetului este construită din pachetul original, chiar executând codul care a construit startingDeck. Aceasta cauzează o mare cantitate de lucru în plus.

In practică, unii algoritmi rulează bine folosind evaluarea imediată. Pentru utilizarea zilnică, evaluarea leneșă este de obicei o alegere mai bună când sursa de date este un proces separat, ca un motor de bază de date. Pentru bazele de date, evaluarea leneșă permite interogări mai complexe să se execute doar cu un singur drum către procesul bazei de date și înapoi la restul codului dvs. LINQ este flexibil fie că alegeți să folosiți evaluare leneșă fie imediată, deci măsurați-vă procesele și alegeți oricare fel de evaluare vă dă cea mai bună performanță.

Concluzie

In acest proiect, dvs. ați acoperit:

  • folosirea interogărilor LINQ pentru a agrega date într-o secvență semnificativă
  • scrierea de metode extensii pentru a adăuga propria noastră funcționalitate personalizată la interogări LINQ
  • localizarea zonelor din codul nostru în care interogările noastre LINQ ar putea ajunge în probleme de performanță ca viteză degradată
  • evaluarea leneșă și imediată în legătură cu interogările LINQ și implicațiile pe care ele le pot avea asupra performanței interogărilor
Pe lângă LINQ, ați învățat puțin despre o tehnică pe care magicienii o folosesc pentru trucuri cu cărți de joc. Magicienii folosesc amestecul Faro (Faro shuffle) deoarece ei pot controla unde fiecare carte se mișcă în pachet. Acum că știți, nu o stricați pentru toți ceilalți!
Pentru mai multe informații despre LINQ, vedeți:

Tradus din această pagină oficială de documentație Microsoft.

[C#] Tutorial „Moștenirea în C# și .NET”

Tradus din această pagină oficială de documentație Microsoft.

Acest tutorial vă introduce în moștenirea în C#. Moștenirea este o facilitate a limbajelor de programare orientate pe obiect care vă permite să definiți o clasă de bază care furnizează funcționalități specifice (date și comportament) și să definiți clase derivate care fie moștenesc fie suprascriu acea funcționalitate.

Cerințe preliminare

Acest tutorial presupune că dvs. ați instalat .NET Core. Pentru instrucțiunile de instalare, vedeți Ghidul de instalare .NET Core. Dvs. de asemenea aveți nevoie de un editor de cod. Acest tutorial folosește Visual Studio Code, dar puteți folosi oricare editor de cod la alegerea dvs.

Rularea exemplelor

Pentru a crea și rula exemplele din acest tutorial, dvs. folosiți utilitarul dotnet din linia de comandă. Urmați acești pași pentru fiecare exemplu:
  1. Creați un director să stocheze exemplul.
  2. Introduceți comanda dotnet new console la o linie de comandă să creați un nou proiect .NET Core.
  3. Copiați și lipiți codul din exemplu în editorul dvs. de cod.
  4. Introduceți comanda dotnet restore din linia de comandă să încărcați sau restaurați dependențele proiectului.
Notă. Începând cu .NET Core 2.0, nu trebuie să rulați dotnet restore deoarece este rulat imlicit de toate comenzile care necesită o restaurare să aibă loc, cum ar fi dotnet new, dotnet build și dotnet run. Este încă o comandă validă în unele scenarii în care a face o restaurare explicită are sens, cum ar fi construcții de integrare continuă în Azure DevOps Services sau în sisteme de construcție care necesită să controleze explicit timpul la care restaurarea are loc.
  1. Introduceți comanda dotnet run să compilați și să executați exemplul.

Informații generale: Ce este moștenirea?

Moștenirea este unul dintre atributele fundamentale ale programării orientate pe obiecte. El vă permite să definiți o clasă copil care refolosește (moștenește), extinde, sau modifică comportamentul unei clase părinte. Clasa ai cărei membri sunt moșteniți se numește clasa bază. Clasa care moștenește membrii clasei bază se cheamă clasa derivată.
C# și .NET suportă doar moștenire singulară. Aceasta înseamnă, o clasă poate moșteni doar de la o singură clasă. Totuși, moștenirea este tranzitivă, ceea ce vă permite să definiți o ierarhie de moșteniri pentru o mulțime de tipuri. Cu alte cuvinte, tipul D poate moșteni de la tipul C, care moștenește de la tipul B, care moștenește de la tipul de clasă bază A. Deoarece moștenirea este tranzitivă, membrii tipului A sunt disponibili tipului D.
Nu toți membrii unei clase bază sunt moșteniți de clasele derivate. Următorii membri nu sunt moșteniți:
  • Constructori statici, are inițializează datele statice ale unei clase.
  • Constructorii de instanță, pe care îi apelați să creeze o nouă instanță a clasei. Fiecare clasă trebuie să își definească proprii ei constructori.
  • Finalizatorii, care sunt apelați de colectorul de gunoi al mediului de execuție să distrugă instanțe ale unei clase.
In timp ce toți ceilalți membri ai unei clase bază sunt moșteniți de clasele derivate, dacă sunt vizibili sau nu depinde de accesibilitatea lor. Accesibilitatea unui membru afectează vizibilitatea lui pentru clasele derivate după cum urmează:
  • Membrii privați sunt vizibili doar în clasele derivate care sunt îmbricate în clasa lor bază. Altfel, ei nu sunt vizibili în clasele derivate. In exemplul următor, A.B este o clasă îmbricată care derivă din A, și C derivă din A. Câmpul privat A.value este vizibil în A.B. Totuși, dacă ștergeți comentariile din metoda C.GetValue și încercați să compilați exemplul, el produce eroare de compilator CS0122: „’A.value’ is inaccessible due to its protection level.” (‘A.value’ este inaccesibil datorită nivelului lui de protecție.
using System;
public class A
{
   private int value = 10;
   public class B : A
   {
       public int GetValue()
       {
           return this.value;
       }    
   }
}
public class C : A
{
//    public int GetValue()
//    {
//        return this.value;
//    }
}
public class Example
{
    public static void Main(string[] args)
    {
        var b = new A.B();
        Console.WriteLine(b.GetValue());
    }
}
// The example displays the following output:
//       10

  • Membrii protejați sunt vizibili doar în clasele derivate.
  • Membrii interni sunt vizibili doar în clasele derivate care se află în același ansamblu precum clasa bază.
  • Membrii publici sunt vizibili în clasele derivate și sunt parte a interfeței publice a clasei derivate. Membrii moșteniți public pot fi apelați ca și cum ei ar fi definiți în clasa derivată. In următorul exemplu, clasa A definește o metodă numită Method1, și clasa B moștenește din clasa A. Acest exemplu apoi apelează Method1 ca și cum ea ar fi fost o metodă instanță pe B.
public class A
{
    public void Method1()
    {
        // Method implementation.
    }
}
public class B : A
{ }

public class Example
{
    public static void Main()
    {
        B b = new B();
        b.Method1();
    }
}
Clasele derivate pot de asemenea suprascrie membri moșteniți oferind o implementare alternativă. Pentru a fi capabil să suprascrie un membru, membrul din clasa bază trebuie să fie marcat cu cuvântul cheie virtual. Implicit, membrii clasei bază nu sunt marcați ca virtual și nu pot fi suprascriși. A încerca să suprascrieți un membru non-virtual, cum face următorul exemplu, generează eroarea de compilator CS0506: „ cannot override inherited member because it is not marked virtual, abstract, or override.” (tradus: nu se poate suprascrie membrul moștenit deoarece el nu este marcat ca virtual, abstract, sau override.)

public class A
{
    public void Method1()
    {
        // Do something.
    }
}


public class B : A
{
    public override void Method1() // Generates CS0506.
    {
        // Do something else.
    }
}


In unele cazuri, o clasă derivată trebuie să suprascrie implementarea clasei bază. Membri clasei bază marcați cu cuvântul cheie abstract necesită ca clasele derivate să îi suprascrie. Incercarea de a compila următorul exemplu generează eroare de compilator CS0534, „ does not implement inherited abstract member ” (tradus: nu implementează membrul abstract moștenit), deoarece clasa B nu furnizează o implementare pentru A.Method1.

public abstract class A
{
    public abstract void Method1();
}


public class B : A // Generates CS0534.
{
    public void Method3()
    {
        // Do something.
    }
}


Moștenirea se aplică doar claselor și interfețelor. Alte categorii de tipuri (structurile, delegații, și enumerările) nu suportă moștenirea. Datorită acestor reguli, a încerca să compilați cod ca următorul exemplu produce eroare de compilator CS0527: „Type ‘ValueType’ in interface list is not an interface.” (tradus: Tipul ‘ValueType’ în lista interfețelor nu este o interfață.) Mesajul de eroare indică faptul că, deși dvs. puteți defini interfața pe care o structură o implementează, moștenirea nu este suportată.

using System;

public struct ValueStructure : ValueType // Generates CS0527.
{
}

Moștenirea implicită

Pe lângă oricare tipuri pe care ele le mai pot moșteni prin moștenire singulară, toate tipurile în sistemul de tipuri .NET moștenesc implicit de la Object sau un tip derivat din el. Funcționalitatea comună a Object este disponibiliă oricărui tip.

Pentru a vedea ce înseamnă moștenirea implicită, haideți să definim o nouă clasă, SimpleClass, care este simplu o definiție de clasă goală:

public class SimpleClass
{}


Dvs. puteți apoi folosi reflecția (care vă permite să inspectați metadatele unui tip pentru a obține informații despre acel tip) pentru a obține o listă a membrilor care aparțin tipului SimpleClass. Deși dvs. n-ați definit nici un membru în clasa dvs. SimpleClass, ieșirea din exemplu indică faptul că ea are de fapt nouă membri. Unul dintre cești membri este un constructor fără parametri (sau implicit) care este furnizat automat pentru tipul SimpleClass de compilatorul C#. Ceilalți opt sunt membri ai Object, tipul din care toate clasele și interfețele în sistemul de tipuri .NET moștenesc implicit în cele din urmă.

using System;
using System.Reflection;


public class Example
{
   public static void Main()
   {
      Type t = typeof(SimpleClass);
      BindingFlags flags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public |
                           BindingFlags.NonPublic | BindingFlags.FlattenHierarchy;
      MemberInfo[] members = t.GetMembers(flags);
      Console.WriteLine($”Type {t.Name} has {members.Length} members: „);
      foreach (var member in members) {
         string access = „”;
         string stat = „”;
         var method = member as MethodBase;
         if (method != null) {
            if (method.IsPublic)
               access = ” Public”;
            else if (method.IsPrivate)
               access = ” Private”;
            else if (method.IsFamily) 
               access = ” Protected”;
            else if (method.IsAssembly)
               access = ” Internal”;
            else if (method.IsFamilyOrAssembly)
               access = ” Protected Internal „;
            if (method.IsStatic)
               stat = ” Static”;
         }
         var output = $”{member.Name} ({member.MemberType}): {access}{stat}, Declared by {member.DeclaringType}”;
         Console.WriteLine(output);

      }
   }
}
// The example displays the following output:
//     Type SimpleClass has 9 members:
//     ToString (Method):  Public, Declared by System.Object
//     Equals (Method):  Public, Declared by System.Object
//     Equals (Method):  Public Static, Declared by System.Object
//     ReferenceEquals (Method):  Public Static, Declared by System.Object
//     GetHashCode (Method):  Public, Declared by System.Object
//     GetType (Method):  Public, Declared by System.Object
//     Finalize (Method):  Internal, Declared by System.Object
//     MemberwiseClone (Method):  Internal, Declared by System.Object
//     .ctor (Constructor):  Public, Declared by SimpleClass


Moștenirea implicită din clasa Object face aceste metode disponibile clasei SimpleClass:

  • Metoda publică ToString, care convertește un obiect SimpleClass la reprezentarea lui șir, întoarce numele tipului complet calificat. In acest caz, metoda ToString întoarce șirul „SimpleClass”.
  • Trei metode care testează egalitatea a două obiecte: metoda publică de instanță Equals(Object), metoda publică statică Equals(Object, Object), și metoda publică statică ReferenceEquals(Object, Object). Implicit, aceste metode testează egalitatea referinței; aceasta înseamnă, să fie egale, două variabile obiect trebuie să se refere la același obiect.
  • Metoda publică GetHashCode, care calculează o valoare care permite unei instanțe a tipului să fie folsită în colecții hash.
  • Metoda publică GetType, care întoarce un obiect Type care reprezintă tipul SimpleClass.
  • Metoda protejată Finalize, care este proiectată să elibereze resurse negestionate înainte ca memoria unui obiect să fie redistribuită de colectorul de gunoi.
  • Metoda protejată MemberwiseClone, care creează o copie superficială a obiectului curent.
Datorită moștenirii implicite, dvs. puteți apela oricare membru moștenit dintr-un obiect SimpleClass ca și cum ar fi fost de fapt un membru definit în clasa SimpleClass. De exemplu, apelează metoda SimpleClass.ToString, pe care SimpleClass o moștenește de la Object.
using System;

public class SimpleClass
{}

public class Example
{
    public static void Main()
    {
        SimpleClass sc = new SimpleClass();
        Console.WriteLine(sc.ToString());
    }
}
// The example displays the following output:
//        SimpleClass

Următorul tabel listează categoriile de tipuri pe care le puteți crea în C# și tipurile de la care ele moștenesc implicit. Fiecare tip de bază face o mulțime diferită de membri disponibilă prin moștenire tipurilor derivate.


Categoria de tip Moștenește implicit de la
class Object
struct ValueType, Object
enum Enum, ValueType, Object
delegate MulticastDelegate, Delegate, Object

Moștenirea și o relație „este un”

In mod obișnuit, moștenirea este folosită să exprime o relație „este un” între o clasă bază și una sau mai multe clase derivate, unde clasele derivate sunt versiuni specializate ale clasei bază; clasa derivată este un tip al clasei bază. De exemplu, clasa Publicație (en. Publication) reprezintă o publicație de orice fel, și clasele Carte (en. Book) și Revistă (en. Magazine) reprezintă tipuri specifice de publicații.

Notă. O clasă sau structură poate implementa una sau mai multe interfețe. In timp ce implementarea interfețelor este deseori prezentată ca o soluție de rezolvare pentru moștenirea singulară sau ca o cale de a folosi moștenirea cu structuri, ea este destinată să exprime o relație diferită (o relație „poate face”) între o interfață și tipul ei de implementare spre deosebire de moștenire. O interfață definește o submulțime a funcționalității (cum este abilitatea de a testa pentru egalitate, de a compara sau sorta obiecte, sau de a suporta analiză și formatare sensibilă la cultură) pe care interfața o face disponibilă tipurilor ei de implementare.

Notați și că „este un” exprimă o relație între un tip și o instanțiere specifică a acelui tip. In următorul exemplu, Automobile este o clasă care are trei proprietăți unice doar-pentru-citire: Make, producătorul automobilului; Model, tipul de automobil; și Year, anul lui de fabricație. Clasa dvs. Automobile are de asemenea un constructor ale cărui argumente sunt atribuite valorilor proprietăților, și ea suprascrie metoda Object.ToString să producă un șir de caractere care identifică în mod unic instanța Automobile în loc de clasa Automobile.

using System;

public class Automobile
{
    public Automobile(string make, string model, int year)
    {
        if (make == null)
           throw new ArgumentNullException(„The make cannot be null.”);
        else if (String.IsNullOrWhiteSpace(make))
           throw new ArgumentException(„make cannot be an empty string or have space characters only.”);
        Make = make;


        if (model == null)
           throw new ArgumentNullException(„The model cannot be null.”);
        else if (String.IsNullOrWhiteSpace(model))
           throw new ArgumentException(„model cannot be an empty string or have space characters only.”);
        Model = model;


        if (year DateTime.Now.Year + 2)
           throw new ArgumentException(„The year is out of range.”);
        Year = year;
    }


    public string Make { get; }
   
    public string Model { get; }


    public int Year { get; }

    public override string ToString() => $”{Year} {Make} {Model}”;
}


In acest caz, dvs. nu ar trebui să vă bazați pe moștenire să reprezentați producători și modele specifice de mașini. De exemplu, dvs. nu trebuie să definiți un tip Packard pentru a reprezenta automobile produse de Packard Motor Car Company. In loc, dvs. puteți să le reprezentați creând un obiect Automobile cu valorile potrivite transmise constructorului său de clasă, cum face următorul exemplu.

using System;

public class Example
{
    public static void Main()
    {
        var packard = new Automobile(„Packard”, „Custom Eight”, 1948);
        Console.WriteLine(packard);
    }
}
// The example displays the following output:
//        1948 Packard Custom Eight


O relație este-un bazată pe moștenire este cel mai bine aplicată unei clase bază și unor clase derivate care adaugă membri în plus clasei bază sau care cer funcționalitate în plus care nu este prezentă în clasa bază.

Proiectarea clasei bază și a claselor derivate

Haideți să privim procesul de proiectare a clasei bază și a claselor derivate. In această secțiune, dvs. veți defini o clasă de bază, Publication, care reprezintă publicații de orice fel, cum este o carte, o revistă, un ziar, un jurnal, un articol etc. Dvs. veți defini de asemenea o clasă Book care derivă din Publication. Dvs. ați putea ușor să extindeți exemplul să definească alte clase derivate, precum Magazine, Journal, Newspaper, și Article.

Clasa de bază Publication

In proiectarea clasei dvs. Publication, dvs. trebuie să faceți câteva decizii de proiectare:

  • Ce membri să includeți în clasa dvs. de bază Publication, și dacă membrii Publication oferă implementări de metode sau dacă Publication  este o clasă de bază abstractă care servește ca un șablon pentru clasele ei derivate.

In acest caz, clasa Publication va oferi implementări de metode. Secțiunea Proiectarea claselor bază abstracte și a claselor lor derivate (mai jos) conține un exemplu care folosește o clasă bază abstractă să definească metodele pe care clasele derivate trebuie să le suprascrie. Clasele derivate sunt libere să ofere orice implementare care este potrivită pentru tipul derivat.

Abilitatea de a refolosi codul (aceasta înseamnă, multiple clase derivate împart declarația și implementarea metodelor clasei bază și nu necesită să le suprascrie) este un avantaj al claselor bază non-abstracte. Prin urmare, ar trebui să adăugați membri la Publication dacă codul lor este probabil să fie împărțit de câteva sau cele mai specializate tipuri Publication. Dacă eșuați să furnizați implementări ale clasei bază eficient, veți sfârși trebuind să furnizați implementări de membri în mare identice în clasele derivate în locul unei singure implementări în clasa bază. Nevoia de a menține cod duplicat în mai multe locuri este o sursă potențială de bug-uri.

Atât pentru a maximiza reutilizarea codului și pentru a crea o ierarhie de moștenire logică și intuitivă, dvs. doriți să vă asigurați că includeți în clasa Publication doar datele și funcționalitatea care este comună tuturor sau celor mai multe publicații. Clasele derivate apoi implementează membri care sunt unici pentru felurile particulare de publicații pe care le reprezintă.

  • Cât de departe să vă extindeți ierarhia dvs. de clase. Doriți să dezvoltați o ierarhie de trei sau mai multe clase, în loc pur și simplu al unei clase bază și una sau mai multe clase derivate? De exemplu, Publication ar putea fi o clasă bază a Periodical, care la rândul ei este o clasă bază al Magazine, Journal și Newspaper.
Pentru exemplul dvs., veți folosi mica ierarhie a clasei Publication și o singură clasă derivată, Book. Dvs. ați fi putut ușor extinde exemplul să creați un număr de clase în plus care derivă din Publication, precum Magazine și Article.
  • Dacă are sens să instanțiați clasa bază. Dacă nu are, dvs. ar trebui să aplicați cuvântul cheie abstract clasei. Altfel, clasa dvs. Publication poate fi instanțiată apelând constructorul de clasă al ei. Dacă o încercare este făcută să instanțieze o clasă marcată cu cuvântul cheie abstract printr-un apel direct la constructorul de clasă al ei, compilatorul C# generează eroarea CS0144, „Cannot create an instance of the abstract class or interface.” (tradus: nu se poate crea o instanță a clasei abstracte sau a interfeței) Dacă o încercare este făcută să instanțieze clasa folosind reflecția, metoda reflecției aruncă o excepție MemberAccessException.
Implicit, o clasă bază poate fi instanțiată apelându-i constructorul ei de clasă. Dvs. nu trebuie să definiți explicit un constructor de clasă. Dacă unul nu este prezent în codul sursă al clasei bază, compilatorul C# furnizează automat un constructor implicit (fără parametri).

Pentru exemplul dvs., veți marca clasa Publication ca abstract astfel încât ea să nu poată fi instanțiată. O clasă abstract fără nici o metodă abstract indică faptul că această clasă reprezintă un concept abstract care este împărțit între câteva clase concrete (precum un Book, Journal).

  • Dacă clasele derivate trebuie să moștenească implementările clasei bază ale unor anumiți membri speciali, dacă ei au opțiunea să suprascrie implementarea clasei bază, sau dacă ei trebuie să ofere o implementare. Dvs. folosiți cuvântul cheie abstract să forțați clasele derivate să ofere o implementare. Folosiți cuvântul cheie virtual să permiteți claselor derivate să suprascrie o metodă a clasei bază. In mod implicit, metodele definite în clasa bază nu sunt supracomandabile.

Clasa Publication nu are nici o metodă abstract, dar clasa însăși este abstract.

  • Dacă clasa derivată reprezintă clasa finală în ierarhia de moștenire și nu poate fi folosită ea însăși ca o clasă bază pentru clase derivate adiționale. Implicit, oricare clasă poate servi ca clasă bază. Puteți aplica cuvântul cheie sealed să indicați că o clasă nu poate servi ca o clasă  bază pentru oricare clase adiționale. A încerca să derivați dintr-o clasă sigilată generează eroarea de compilator CS0509, „cannot derive from sealed type ” (tradus: nu se poate deriva din tip sigilat).
Pentru exemplul dvs., veți marca clasa dvs. derivată ca sealed.

    Următorul exemplu arată codul sursă pentru clasa Publication, și de asemenea o enumerație PublicationType care este întoarsă de proprietatea Publication.PublicationType. Pe lângă membrii pe care îi moștenește de la Object, clasa Publication definește următorii membri unici și suprascrieri de membri:

    using System;

    public enum PublicationType { Misc, Book, Magazine, Article };

    public abstract class Publication
    {
       private bool published = false;
       private DateTime datePublished;
       private int totalPages;


       public Publication(string title, string publisher, PublicationType type)
       {
          if (publisher == null)
             throw new ArgumentNullException(„The publisher cannot be null.”);
          else if (String.IsNullOrWhiteSpace(publisher))
             throw new ArgumentException(„The publisher cannot consist only of white space.”);
          Publisher = publisher;

          if (title == null)
             throw new ArgumentNullException(„The title cannot be null.”);
          else if (String.IsNullOrWhiteSpace(title))
             throw new ArgumentException(„The title cannot consist only of white space.”);
          Title = title;

          Type = type;
       }


       public string Publisher { get; }

       public string Title { get; }

       public PublicationType Type { get; }

       public string CopyrightName { get; private set; }
      
       public int CopyrightDate { get; private set; }


       public int Pages
       {
         get { return totalPages; }
         set
         {
             if (value <= 0)
                throw new ArgumentOutOfRangeException(„The number of pages cannot be zero or negative.”);
             totalPages = value;  
         }
       }


       public string GetPublicationDate()
       {
          if (!published)
             return „NYP”;
          else
             return datePublished.ToString(„d”);  
       }
      
       public void Publish(DateTime datePublished)
       {
          published = true;
          this.datePublished = datePublished;
       }


       public void Copyright(string copyrightName, int copyrightDate)
       {
          if (copyrightName == null)
             throw new ArgumentNullException(„The name of the copyright holder cannot be null.”);
          else if (String.IsNullOrWhiteSpace(copyrightName))
             throw new ArgumentException(„The name of the copyright holder cannot consist only of white space.”);
          CopyrightName = copyrightName;

          int currentYear = DateTime.Now.Year;
          if (copyrightDate currentYear + 2)
             throw new ArgumentOutOfRangeException($”The copyright year must be between {currentYear – 10} and {currentYear + 1}”);
          CopyrightDate = copyrightDate;     
       }

       public override string ToString() => Title;
    }

    • Un constructor
    Deoarece clasa Publication este abstract, ea nu poate fi instanțiată direct din cod ca în următorul exemplu:
    var publication = new Publication(„Tiddlywinks for Experts”,
                                      „Fun and Games”,
                                      PublicationType.Book);
    Totuși, constructorul ei de instanțe poate fi apelat direct din constructorii claselor derivate, așa cum codul sursă pentru clasa Book arată.
    • Două proprietăți legate de publicare
    Title este o proprietate String doar-pentru-citire a cărei valori este furnizată apelând constructorul Publication.
    Pages este o proprietate Int32 doar-pentru-citire care indică cât de multe pagini întregi are publicația. Valoarea este reținută într-un câmp privat numit totalPages. Ea trebuie să fie un număr pozitiv altfel o excepție ArgumentOutOfRangeException este aruncată.
    • Membri legați de editor
    Două proprietăți doar-pentru-citire, Publisher și Type. Valorile sunt la început furnizate de apelul către constructorul de clasă Publication.
    • Membri legați de publicare
    Două metode, Publish și GetPublicationDate, setează și întorc data publicării. Metoda Publish setează un steag privat published la true când este apelată și atribuie data transmisă ei ca un argument la câmpul privat datePublished. Metoda GetPublicationDate întoarce șirul „NYP” dacă steagul published este false, și valoarea câmpului datePublished dacă el este true.
    • Membri legați de drepturile de autor
    Metoda Copyright ia numele drepturilor de autor și anul drepturilor de autor ca argumente și le atribuie proprietăților CopyrightName și CopyrightDate.
    • O suprascriere a metodei ToString
    Dacă un tip nu suprascrie metoda Object.ToString, ea întoarce numele complet calificat al tipului, ceea ce este puțin folositor în diferențierea unei instanțe de alta. Clasa Publication suprascrie Object.ToString să întoarcă valoarea proprietății Title.

    Următoarea figură ilustrează relația dintre clasa dvs. bază Publication și clasa ei implicit moștenită Object.

    Clasa Book

    Clasa Book reprezintă o carte ca un tip specializat de publicație. Următorul exemplu prezintă codul sursă pentru clasa Book.
    using System;

    public sealed class Book : Publication
    {
       public Book(string title, string author, string publisher) :
              this(title, String.Empty, author, publisher)
       { }

       public Book(string title, string isbn, string author, string publisher) : base(title, publisher, PublicationType.Book)
       {
          // isbn argument must be a 10- or 13-character numeric string without „-” characters.
          // We could also determine whether the ISBN is valid by comparing its checksum digit
          // with a computed checksum.
          //
          if (! String.IsNullOrEmpty(isbn)) {
            // Determine if ISBN length is correct.
            if (! (isbn.Length == 10 | isbn.Length == 13))
                throw new ArgumentException(„The ISBN must be a 10- or 13-character numeric string.”);
            ulong nISBN = 0;
            if (! UInt64.TryParse(isbn, out nISBN))
                throw new ArgumentException(„The ISBN can consist of numeric characters only.”);
          }
          ISBN = isbn;

          Author = author;
       }
        
       public string ISBN { get; }

       public string Author { get; }
      
       public Decimal Price { get; private set; }

       // A three-digit ISO currency symbol.
       public string Currency { get; private set; }
      
       // Returns the old price, and sets a new price.
       public Decimal SetPrice(Decimal price, string currency)
       {
           if (price < 0)
              throw new ArgumentOutOfRangeException(„The price cannot be negative.”);
           Decimal oldValue = Price;
           Price = price;
          
           if (currency.Length != 3)
              throw new ArgumentException(„The ISO currency symbol is a 3-character string.”);
           Currency = currency;

           return oldValue;     
       }

       public override bool Equals(object obj)
       {
          Book book = obj as Book;
          if (book == null)
             return false;
          else
             return ISBN == book.ISBN;  
       }

       public override int GetHashCode() => ISBN.GetHashCode();

       public override string ToString() => $”{(String.IsNullOrEmpty(Author) ? „” : Author + „, „)}{Title}”;
    }


    Pe lângă membrii pe care îi moștenește de la Publication, clasa Book definește următorii membri unici și suprascrieri de membri:

    • Doi constructori
    Cei doi constructori Book împart trei parametri comuni. Doi, title și publisher, corespund parametrilor constructorului Publication. Al treilea este author, care este reținut într-un câmp privat authorName. Un constructor include un parametru isbn, care este stocat în auto-proprietatea ISBN.

    Primul constructor folosește cuvântul cheie this pentru a apela celălalt constructor. Înlănțuirea constructorilor este un tipar comun în definirea constructorilor. Constructorii cu mai puțini parametri furnizează valori implicite când se apelează constructorul cu cel mai mare număr de parametri.

    Al doilea constructor folosește cuvântul cheie base pentru a transmite titlul și numele editorului către constructorul clasei bază. Dacă dvs. nu faceți un apel explicit la o constructorul unei clase bază în codul dvs. sursă, compilatorul dvs. C# furnizează automat un apel la constructorul implicit sau fără parametri al clasei bază.

    • O proprietate doar-pentru-citire ISBN, care întoarce numărul de carte standard internațional (en.: International Standard Book Number) al obiectului Book, un număr unic de 10 sau 13 cifre. ISBN-ul este furnizat ca un argument la unul dintre constructorii Book. ISBN-ul este stocat într-un câmp privat de sprijin, care este generat automat de compilator.
    • O proprietate doar-pentru-citire Author. Numele autorului este furnizat ca un argument la amândoi constructorii Book și este stocat în câmpul privat authorName.
    • Două proprietăți doar-pentru-citire legate de preț, Price și Currency. Valorile lor sunt furnizate ca argumente într-un apel de metodă SetPrice. Prețul este stocat într-un câmp privat, bookPrice. Proprietatea Currency este simbolul monedă ISO de 3 caractere (de exemplu, USD pentru dolarul american) și este stocat în câmpul privat ISOCurrencySymbol. Simbolurile ISO de monede pot fi preluate din proprietatea ISOCurrencySymbol.
    • O metodă SetPrice, care setează valorile câmpurilor bookPrice și ISOCurrencySymbol. Aceste valori sunt întoarse de proprietățile Price și Currency.
    • Suprascrie metoda ToString (moștenită de la Publication) și metodele Object.Equals(Object) și GetHashCode (moștenite de la Object).
    Dacă nu este suprascrisă, metoda Object.Equals(Object) testează egalitatea referințelor. Aceasta înseamnă, două variabile obiect sunt considerate a fi egale dacă ele se referă la același obiect. In clasa Book, pe de altă parte, două obiecte Book ar trebui să fie egale dacă ele au același ISBN.
    Când suprascrieți metoda Object.Equals(Object), dvs. trebuie de asemenea să suprascrieți metoda GetHashCode, care întoarce o valoare pe care mediul de execuție o folosește să stocheze elemente în colecții hash pentru obținere eficientă. Codul hash ar trebui să întoarcă o valoare care este consistentă cu testul pentru egalitate. Fiindcă dvs. ați suprascris Object.Equals(Object) să întoarcă true dacă proprietățile ISBN ale două obiecte Book sunt egale, dvs. întoarceți codul hash calculat prin apelarea metodei GetHashCode a șirului întors de proprietatea ISBN.
    Figura următoare ilustrează relația dintre clasa Book și Publication, clasa ei bază.
    Dvs. puteți acum instanția un obiect Book, invoca și membrii lui unici și cei moșteniți, și să-l transmiteți ca un argument către o metodă care așteaptă un parametru de tip Publication sau de tip Book, după cum arată următorul exemplu.
    using System;
    using static System.Console;

    public class Example
    {
       public static void Main()
       {
          var book = new Book(„The Tempest”,  „0971655819”, „Shakespeare, William”,
                              „Public Domain Press”);
          ShowPublicationInfo(book);
          book.Publish(new DateTime(2016, 8, 18));
          ShowPublicationInfo(book);

          var book2 = new Book(„The Tempest”, „Classic Works Press”, „Shakespeare, William”);
          Write($”{book.Title} and {book2.Title} are the same publication: ” +
                $”{((Publication) book).Equals(book2)}”);
       }

       public static void ShowPublicationInfo(Publication pub)
       {
           string pubDate = pub.GetPublicationDate();
           WriteLine($”{pub.Title}, ” +
                     $”{(pubDate == „NYP” ? „Not Yet Published” : „published on ” + pubDate):d} by {pub.Publisher}”);
       }
    }
    // The example displays the following output:
    //        The Tempest, Not Yet Published by Public Domain Press
    //        The Tempest, published on 8/18/2016 by Public Domain Press
    //        The Tempest and The Tempest are the same publication: False

    Proiectarea claselor bază abstracte și a claselor lor derivate

    In exemplul precedent, dvs. ați definit o clasă bază care furniza o implementare pentru un număr de metode pentru a permite claselor derivate să împartă cod. In multe cazuri, totuși, clasa bază nu este așteptată să furnizeze o implementare. In schimb, clasa bază este o clasă abstractă care declară metode abstracte; ea servește ca un șablon care definește membrii pe care fiecare clasă derivată trebuie să îi implementeze. Tipic într-o clasă bază abstractă, implementarea fiecărui tip derivat este unică pentru acel tip. Dvs. ați marcat clasa cu cuvântul cheie abstract deoarece nu avea sens să instanțiați o clasă Publication, cu toate că clasa într-adevăr oferea implementări de funcționalitate comună publicațiilor.

    De exemplu, fiecare formă geometrică bidimensională închisă include două proprietăți: aria, extinderea interioară a formei; și perimetrul, sau distanța de-a lungul muchiilor formei. Felul în care aceste proprietăți sunt calculate, totuși, depind complet de forma specifică. Formula pentru calcularea perimetrului (sau circumferinței) unui cerc, de exemplu, este diferită de aceea a unui triunghi. Clasa Shape este o clasă abstract cu metode abstract. Aceasta indică faptul că clasele derivate împart aceeași funcționalitate, dar aceste clase derivate implementează acea funcționalitate diferit.

    Următorul exemplu definește o clasă bază abstract numită Shape care definește două proprietăți: Area și Perimeter. Pe lângă a marca clasa cu cuvântul cheie abstract, fiecare membru de instanță este de asemenea marcat cu cuvântul cheie abstract. In acest caz, Shape de asemenea suprascrie metoda Object.ToString să întoarcă numele tipului, în locul numelui său complet calificat. Si definește doi membri statici, GetArea și GetPerimeter, care permit apelanților să obțină ușor aria și perimetrul unei instanțe a oricărei clase derivate. Când transmiteți o instanță a unei clase derivate la oricare din aceste metode, mediul de execuție apelează suprascrierea metodei a clasei derivate.

    using System;

    public abstract class Shape
    {
       public abstract double Area { get; }
      
       public abstract double Perimeter { get; }

       public override string ToString() => GetType().Name;

       public static double GetArea(Shape shape) => shape.Area;

       public static double GetPerimeter(Shape shape) => shape.Perimeter;
    }


    Dvs. puteți apoi deriva câteva clase din Shape care reprezintă forme specifice. Următorul exemplu definește trei clase, Triangle, Rectangle, și Circle. Fiecare folosește o formulă unică pentru acea formă particulară pentru a calcula aria și perimetrul. Unele dintre clasele derivate de asemenea definesc proprietăți, cum sunt Rectangle.Diagonal și Circle.Diameter, care sunt unice formei pe care o reprezintă.

    using System;

    public class Square : Shape
    {
       public Square(double length)
       {
          Side = length;
       }


       public double Side { get; }  

       public override double Area => Math.Pow(Side, 2); 

       public override double Perimeter => Side * 4;

       public double Diagonal => Math.Round(Math.Sqrt(2) * Side, 2);
    }


    public class Rectangle : Shape
    {
       public Rectangle(double length, double width)
       {
          Length = length;
          Width = width;
       }


       public double Length { get; }

       public double Width { get; }

       public override double Area => Length * Width;

       public override double Perimeter => 2 * Length + 2 * Width;
      
       public bool IsSquare() => Length == Width;

       public double Diagonal => Math.Round(Math.Sqrt(Math.Pow(Length, 2) + Math.Pow(Width, 2)), 2);
    }


    public class Circle : Shape
    {
       public Circle(double radius)
       {
          Radius = radius;
       } 


       public override double Area => Math.Round(Math.PI * Math.Pow(Radius, 2), 2); 

       public override double Perimeter => Math.Round(Math.PI * 2 * Radius, 2); 

       // Define a circumference, since it’s the more familiar term.
       public double Circumference => Perimeter; 


       public double Radius { get; }

       public double Diameter => Radius * 2;
    }


    Următorul exemplu folosește obiecte derivate din Shape. El instanțiază un vector de obiecte derivate din Shape și apelează metodele statice ale clasei Shape, care întoarce valorile proprietăților Shape. Mediul de execuție preia valori ale proprietăților suprascrise ale tipurilor derivate. Exemplul de asemenea convertește fiecare obiect Shape din vector în tipul său derivat și, dacă acea convertire reușește, întoarce proprietăți ale acelei subclase particulare a Shape.

    using System;

    public class Example
    {
       public static void Main()
       {
          Shape[] shapes = { new Rectangle(10, 12), new Square(5),
                            new Circle(3) };
          foreach (var shape in shapes) {
             Console.WriteLine($”{shape}: area, {Shape.GetArea(shape)}; ” +
                               $”perimeter, {Shape.GetPerimeter(shape)}”);
             var rect = shape as Rectangle;
             if (rect != null) {
                Console.WriteLine($”   Is Square: {rect.IsSquare()}, Diagonal: {rect.Diagonal}”);
                continue;
             }
             var sq = shape as Square;
             if (sq != null) {
                Console.WriteLine($”   Diagonal: {sq.Diagonal}”);
                continue;
             }
          }  
       }
    }
    // The example displays the following output:
    //         Rectangle: area, 120; perimeter, 44
    //            Is Square: False, Diagonal: 15.62
    //         Square: area, 25; perimeter, 20
    //            Diagonal: 7.07
    //         Circle: area, 28.27; perimeter, 18.85

    Vedeți de asemenea

    Tradus din această pagină oficială de documentație Microsoft.

    [C#] Tutorial „Client REST”

    Introducere

    Acest tutorial va învață câteva facilități din .NET Core și din limbajul C#. Veți învăța:

    • Bazele interfeței linie de comandă (CLI) a .NET Core.
    • O vedere de ansamblu a facilităților limbajului C#.
    • Organizarea dependențelor cu NuGet
    • Comunicații HTTP
    • Procesarea informației JSON
    • Organizarea configurației cu Atribute.
    Veți construi o aplicație care trimite Cereri HTTP la un serviciu REST pe GitHub. Veți citi informații în format JSON, și converti acel pachet JSON în obiecte C#. In final, veți vedea cum să lucrați cu obiecte C#.
    Sunt multe facilități în acest tutorial. Haideți să le construim una câte una.

    Dacă preferați să urmăriți cu exemplul final pentru acest subiect, îl puteți descărca. Pentru instrucțiuni de descărcare, vedeți Samples and Tutorials.

    Cerințe preliminare

    Va trebui să vă configurați mașina să ruleze .NET Core. Dvs. puteți găsi instrucțiunile de instalare pe pagina .NET Core. Puteți rula această aplicație pe Windows, Linux, macOS sau într-un container Docker. Va trebui să vă instalați editorul dvs. favorit de cod. Descrierile de mai jos folosesc Visual Studio Code, care este un editor cu sursă deschisă, cross platform. Totuși, puteți folosi oricare unelte cu care vă simțiți confortabil.

    Creați aplicația

    Primul pas este să creați o nouă aplicație. Deschideți o linie de comandă și creați un nou director pentru aplicația dvs. Faceți-l directorul curent. Tastați comanda dotnet new console la linia de comandă. Aceasta creează fișierele de început pentru o aplicație de bază „Hello World”.

    Inainte de a începe să faceți modificări, haideți să mergem prin pașii de a rula simpla aplicație Hello World. După crearea aplicației, tastați dotnet restore (vedeți nota) la linia de comandă. Această comandă rulează procesul de restaurare a pachetelor NuGet. NuGet este un manager de pachete .NET. Această comandă descarcă fiecare din dependențele lipsă pentru proiectul dvs. Fiindcă acesta este un proiect nou, nici una din dependențe nu sunt la locul lor, deci prima rulare va descărca framework-ul (cadrul de lucru) .NET Core. După acest pas inițial, dvs. va trebui să rulați dotnet restore (vedeți nota) doar când adăugați noi pachete-dependență, sau când actualizați versiunile oricăreia dintre dependențele dvs.

    După restaurarea pachetelor, dvs. rulați dotnet build. Aceasta execută motorul de construcție și creează aplicația dvs. In final, dvs. executați dotnet run pentru a rula aplicația dvs.

    Adăugarea de noi dependențe

    Unul dintre scopurile cheie de design este să minimizeze dimensiunea instalației .NET. Dacă o aplicație are nevoie de biblioteci în plus pentru unele dintre facilitățile ei, dvs. adăugați aceste dependențe în fișierul dvs. de proiect C# (*.csproj). Pentru exemplul nostru, dvs. va trebui să adăugați pachetul System.Runtime.Serialization.Json astfel încât aplicația dvs. poate procesa răspunsuri JSON.

    Deschideți fișierul dvs. proiect csproj. Prima linie a fișierului ar trebui să apară ca:



    Adăugați următoarele imediat după această linie:


       


    Majoritatea editorilor de cod vor oferi completare pentru diferite versiuni ale acestor biblioteci. De obicei dvs. veți dori să folosiți cea mai recentă versiune a oricărui pachet pe care îl adăugați. Totuși, este important să vă asigurați că versiunile tuturor pachetelor se potrivesc, și că ele se potrivesc și cu versiunea cadrului de lucru .NET Core Application.

    După ce ați făcut aceste schimbări, dvs. ar trebui să rulați dotnet restore (vedeți nota) din nou astfel încât pachetul să fie instalat pe sistemul dvs.

    Facerea de cereri Web

    Acum sunteți pregătit să începeți să preluați date de pe web. In această aplicație, dvs. veți citi informații de la API-ul GitHub. Haideți să citim informații despre proiectele de sub umbrela Fundația .NET. Dvs. veți începe făcând cererea la API-ul GitHub pentru a prelua informații despre proiecte. Endpoint-ul pe care îl veți folosi este: https://api.github.com/orgs/dotnet/repos. Dvs. doriți să preluați toate informațiile despre aceste proiecte, deci veți folosi o cerere HTTP GET. Navigatorul dvs. folosește de asemenea cereri HTTP GET, deci dvs. puteți lipi acel URL în navigatorul dvs. pentru a vedea ce informații veți prelua și procesa.

    Dvs. folosiți clasa HttpClient pentru a face cereri web. Ca toate API-urile .NET moderne, HttpClient suportă doar metode asincrone pentru API-urile sale de lungă durată. Începeți prin a face o metodă asincronă. Veți completa implementarea pe măsură ce construiți funcționalitatea aplicației. Începeți prin a deschide fișierul program.cs din directorul proiectului dvs. și adăugând următoarea metodă la clasa Program:

    private static async Task ProcessRepositories()
    {


    }

    Dvs. va trebui să adăugați o instrucțiune using în partea de sus a metodei dvs. Main astfel încât compilatorul C# să recunoască tipul Task:

    using System.Threading.Tasks;

    Dacă dvs. construiți proiectul dvs. în acest punct, veți primi un avertisment generat pentru această metodă, deoarece ea nu conține niciun operator await și va rula sincron. Ignorați aceasta pentru acum; dvs. veți adăuga operatori await pe măsură ce completați metoda.

    In continuare, redenumiți namespace-ul definit în instrucțiunea namespace din implicitul ei ConsoleApp în WebAPIClient. Mai târziu vom defini o clasă repo în acest namespace.

    In continuare, actualizați metoda Main să apeleze această metodă. Metoda ProcessRepositories întoarce un Task (sarcină), și dvs. nu ar trebui să ieșiți din program înainte ca sarcina să se finalizeze. In concluzie, trebuie să folosiți metoda Wait pentru a bloca și a aștepta pentru sarcină să se finalizeze:

    static void Main(string[] args)
    {
        ProcessRepositories().Wait();
    }


    Acum, dvs. aveți un program care nu face nimic, dar îl face asincron. Haideți să-l îmbunătățim.

    Mai întâi vă trebuie un obiect care este capabil să preia date de pe web; dvs. puteți folosi un HttpClient pentru a face aceasta. Acest obiect tratează cererile și răspunsurile. Instanțiați o singură instanță a acelui tip în clasa Program în interiorul fișierului Program.cs.

    namespace WebAPIClient
    {
        class Program
        {
            private static readonly HttpClient client = new HttpClient();


            static void Main(string[] args)
            {
                //…
            }
        }
    }


    Haideți să mergem înapoi la metoda ProcessRepositories și să completăm o primă versiune a ei:

    private static async Task ProcessRepositories()
    {
        client.DefaultRequestHeaders.Accept.Clear();
        client.DefaultRequestHeaders.Accept.Add(
            new MediaTypeWithQualityHeaderValue(„application/vnd.github.v3+json”));
        client.DefaultRequestHeaders.Add(„User-Agent”, „.NET Foundation Repository Reporter”);


        var stringTask = client.GetStringAsync(„https://api.github.com/orgs/dotnet/repos&#8221;);

        var msg = await stringTask;
        Console.Write(msg);

    }

    Dvs. va trebui de asemenea să adăugați două noi instrucțiuni la începutul fișierului pentru ca acesta să se compileze:

    using System.Net.Http;
    using System.Net.Http.Headers;

    Această primă versiune face o cerere web să citească lista tuturor depozitelor sub organizația fundației dotnet. (ID-ul GitHub pentru Fundația .NET este „dotnet”). Primele câteva linii configurează HttpClient-ul pentru această cerere. Mai întâi, el este configurat să accepte răspunsurile JSON GitHub. Acest format este simplu JSON. Următoarea linie adăugă un antet User Agent la toate cererile de la acest obiect. Aceste două antete sunt verificate de codul server GitHub, și sunt necesare pentru preluarea de informații de la GitHub.
    După ce ați configurat HttpClient-ul, dvs. faceți o cerere web și primiți răspunsul. In această primă versiune, dvs. folosiți metoda convenabilă HttpClient.GetStringAsync(String). Această metodă confortabilă începe o sarcină care face cererea web, și atunci când cererea revine, ea citește fluxul răspuns și extrage conținutul din flux. Corpul răspunsului este întors ca un String. Șirul de caractere este disponibil când sarcina se termină.

    Cele două linii finale ale acestei metode așteaptă după acea sarcină, și apoi tipăresc răspunsul la consolă. Construiți aplicația, și rulați-o. Avertismentul din construcție este dispărut acum, deoarece ProcessRepositories acum chiar conține un operator await. Veți vedea o lungă afișare de text în format JSON.

    Procesarea rezultatului JSON

    Până în acest punct, dvs. ați scris cod să preia răspuns de la un server web, si să afișeze textul care este conținut în acel răspuns. In continuare, haideți să convertim acest răspuns JSON în obiecte C#.

    Serializatorul JSON convertește date JSON în obiecte C# (tipul object). Prima dvs. sarcină este să definiți un tip clasă C# care să conțină informațiile pe care dvs. le folosiți din acest răspuns. Haideți să construim aceasta încet, deci începeți cu un tip simplu C# care conține numele depozitului:

    using System;

    namespace WebAPIClient
    {
        public class repo
        {
            public string name;
        }

    }


    Puneți codul de mai sus într-un nou fișier numit „repo.cs”. Această versiune a clasei reprezintă cea mai simplă cale de a procesa date JSON. Numele clasei și numele membrului se potrivesc cu numele folosite în pachetul JSON, în loc de a urma convențiile C#. Veți repara aceasta oferind câteva atribute de configurare mai târziu. Această clasă demonstrează o altă importantă facilitate a serializării și deserializării JSON: Nu toate câmpurile din pachetul JSON sunt parte a acestei clase. Serializatorul JSON va ignora informația care nu este inclusă în tipul clasă folosit. Această facilitate face mai ușor să se creeze tipuri care lucrează cu doar o submulțime a câmpurilor din pachetul JSON.

    Acum că dvs. ați creat tipul, haideți să-l deserializăm. Dvs. va trebui să creați un obiect DataContractJsonSerializer. Acest obiect trebuie să știe tipul CLR așteptat pentru pachetul JSON pe care îl preia. Acest pachet de la GitHub conține o secvență de depozite, deci un List este tipul corect. Adăugați următoarea linie la metoda dvs. ProcessRepositories:

    var serializer = new DataContractJsonSerializer(typeof(List));

    Dvs. folosiți două noi namespace-uri, deci va trebui să adăugați următoarele de asemenea:

    using System.Collections.Generic;
    using System.Runtime.Serialization.Json;


    In continuare, dvs. veți folosi serializatorul pentru a converti JSON în obiecte C#. Inlocuiți apelul la GetStringAsync(String) în metoda dvs. ProcessRepositories cu următoarele două linii:

    var streamTask = client.GetStreamAsync(„https://api.github.com/orgs/dotnet/repos&#8221;);
    var repositories = serializer.ReadObject(await streamTask) as List;



    Observați că acum dvs. folosiți GetStreamAsync(String) în locul lui GetStringAsync(String). Serializatorul folosește un flux (en. stream) în loc de un șir de caractere ca sursă. Haideți să explicăm două facilități ale limbajului C# care sunt folosite în a doua linie de mai sus. Argumentul către ReadObject(Stream) este o expresie await. Expresiile await pot apărea aproape oriunde în codul dvs, chiar dacă până acum, dvs. le-ați văzut doar ca parte a unei instrucțiuni de atribuire.

    In al doilea rând, operatorul as convertește de la tipul din timpul compilării object la List. Declarația lui ReadObject(Stream) declară că ea întoarce un obiect de tipul System.Object. ReadObject(Stream) va întoarce tipul specificat de dvs. când dvs. l-ați construit (List în acest tutorial). Dacă conversia nu reușește, operatorul as evaluează la null, în loc de a arunca o excepție.

    Dvs. aproape ați analizat această secțiune. Acum că dvs. ați convertit JSON-ul în obiecte C#, haideți să afișăm numele fiecărui depozit. Inlocuiți liniile care spun:

    var msg = await stringTask;
    Console.Write(msg);

    cu următoarele:

    foreach (var repo in repositories)
        Console.WriteLine(repo.name);


    Compilați și rulați aplicația. Ea va tipări numele depozitelor care sunt parte din Fundația .NET.

    Controlarea serializării

    Inainte de a adăuga mai multe facilități, haideți să ne adresăm tipului repo și să-l facem să urmeze convenții C# mai standarde. Veți face aceasta adnotând tipul repo cu atribute care controlează cum serializatorul JSON lucrează. In cazul dvs., veți folosi aceste atribute pentru a defini o mapare între numele cheilor JSON și numele claselor și membrilor C#. Cele două atribute folosite sunt atributul DataContract și atributul DataMember. Prin convenție, toate clasele Attribute se termină cu sufixul Attribute. Totuși, nu trebuie să folosiți acel sufix când aplicați un atribut.

    Atributele DataContract și DataMember sunt într-o bibliotecă diferită, deci dvs. va trebui să adăugați acea bibliotecă la fișierul dvs. de proiect C# ca o dependență. Adăugați următoarea linie la secțiunea a fișierului dvs. proiect:



    După ce salvați fișierul, rulați dotnet restore (vedeți nota) pentru a obține acest pachet.

    In continuare, deschideți fișierul repo.cs. Haideți să schimbăm numele să folosească Pascal Case, și să spună complet numele Repository. Noi încă mai dorim să mapăm nodurile JSON „repo” la acest tip, deci dvs. va trebui să adăugați atributul DataContract la declarația clasei. Dvs. veți seta proprietatea Name a atributului cu numele nodurilor JSON care mapează la acest tip:

    [DataContract(Name=”repo”)]
    public class Repository

    DataContractAttribute este un membru al namespace-ului System.Runtime.Serialization, deci dvs. va trebui să adăugați instrucțiunea using potrivită la începutul fișierului.

    using System.Runtime.Serialization;

    Dvs. ați schimbat numele clasei repo în Repository, deci dvs. va trebui să faceți aceeași schimbare de nume în Program.cs (unii editori ar putea suporta refactorizarea de redenumire care va face această schimbare automat:)

    var serializer = new DataContractJsonSerializer(typeof(List));

    // …

    var repositories = serializer.ReadObject(await streamTask) as List;
    Acum, haideți să facem aceeași schimbare cu câmpul name folosind clasa DataMemberAttribute. Faceți următoarele schimbări declarației câmpului name în repo.cs:

    [DataMember(Name=”name”)]

    public string Name;


    Această schimbare înseamnă că dvs. va trebui să schimbați codul care scrie numele fiecărui depozit în program.cs:
    Console.WriteLine(repo.Name);
    Dați un dotnet build urmat de un dotnet run să vă asigurați că ați făcut mapările corect. Dvs. ar trebui să vedeți aceeași ieșire ca înainte. Inainte de a procesa mai multe proprietăți de pe serverul web, haideți să facem o schimbare în plus la clasa Repository. Membrul Name este un câmp public accesibil. Aceasta nu este o bună practică orientată pe obiecte, deci haideți să îl schimbăm într-o proprietate. Pentru scopurile noastre, nu ne trebuie niciun cod specific să ruleze când citim sau scriem proprietatea, dar schimbând-o într-o proprietate face mai ușor să adăugăm aceste schimbări mai târziu fără să stricăm vreun cod care folosește clasa Repository.
    Ștergeți definiția de câmp, și înlocuiți-o cu o proprietate auto-implementată:


    public string Name { get; set; }

    Compilatorul generează corpurile accesorilor get și set, și de asemenea un câmp privat să rețină numele. Ar fi similar cu următorul cod pe care dvs. l-ați putea tasta manual:

    public string Name
    {
        get { return this._name; }
        set { this._name = value; }
    }

    private string _name;


    Haideți să mai facem o schimbare înainte de a adăuga noi facilități. Metoda ProcessRepositories poate face muncă asincron și să întoarcă o colecție de depozite. Haideți să întoarcem List-ul din acea metodă, și să mutăm codul care scrie informația în metoda Main.
    Schimbați semnătura lui ProcessRepositories să întoarcă o sarcină al cărei rezultat este o listă de obiecte Repository:
    private static async Task<List> ProcessRepositories()


    Apoi, doar întoarceți depozitele după procesarea răspunsului JSON:

    var repositories = serializer.ReadObject(await streamTask) as List;
    return repositories;


    Compilatorul generează obiectul Task pentru întoarcere deoarece dvs. ați marcat această metodă ca async. Apoi, haideți să modificăm metoda Main astfel încât ea capturează aceste rezultate și scrie numele fiecărui depozit la consolă. Metoda dvs. Main arată acum astfel:

    public static void Main(string[] args)
    {
        var repositories = ProcessRepositories().Result;


        foreach (var repo in repositories)
            Console.WriteLine(repo.Name);
    }


    Accesarea proprietății Result a unui Task (sarcină) blochează până când sarcina s-a finalizat. In mod normal, dvs. ați prefera să „await” (să așteptați) completarea sarcinii, la fel ca în metoda ProcessRepositories, dar acest lucru nu este permis în metoda Main.

    Citirea mai multor informații

    Haideți să finalizăm aceasta procesând încă câteva din proprietățile din pachetul JSON care este trimis din API-ul GitHub. Dvs. nu veți dori să apucați tot, dar adăugând câteva proprietăți va demonstra încă câteva facilități ale limbajului C#.

    Haideți să începem prin a adăuga încă câteva tipuri simple la definiția clasei Repository. Adăugați aceste proprietăți la acea clasă:

    [DataMember(Name=”description”)]
    public string Description { get; set; }


    [DataMember(Name=”html_url”)]
    public Uri GitHubHomeUrl { get; set; }


    [DataMember(Name=”homepage”)]
    public Uri Homepage { get; set; }


    [DataMember(Name=”watchers”)]
    public int Watchers { get; set; }


    Aceste proprietăți au conversii incorporate din tipul șir (ceea ce este ceea ce pachetele JSON conțin) către tipul țintă. Tipul Uri s-ar putea să fie nou pentru dvs. El reprezintă un URI, sau în acest caz, un URL. In cazul tipurilor Uri și int, dacă pachetul JSON conține date care nu se convertesc la tipul țintă, acțiunea de serializare va arunca o excepție.

    Odată ce ați adăugat acestea, actualizați metoda Main să afișeze aceste elemente:

    foreach (var repo in repositories)
    {
        Console.WriteLine(repo.Name);
        Console.WriteLine(repo.Description);
        Console.WriteLine(repo.GitHubHomeUrl);
        Console.WriteLine(repo.Homepage);
        Console.WriteLine(repo.Watchers);
        Console.WriteLine();
    }


    Ca un pas final, haideți să adăugăm informația cu ultima operație push. Această informație este formatată în acest mod în răspunsul JSON:

    2016-02-08T21:27:00Z

    Acest format nu urmează nici unul din formatele standard .NET DateTime. De aceea, dvs. va trebui să scrieți o metodă personalizată de convertire. De asemenea dvs. probabil nu doriți șirul brut expus utilizatorilor clasei Repository. Atributele pot ajuta să controlăm aceasta de asemenea. Mai întâi, definiți o proprietate private care va reține reprezentarea șir a datei și timpului în clasa dvs. Repository:

    [DataMember(Name=”pushed_at”)]
    private string JsonDate { get; set; }


    Atributul DataMember informează serializatorul că aceasta ar trebui să fie procesată, chiar dacă nu este un membru public. In continuare, dvs. trebuie să scrieți o proprietate publică doar-pentru-citire care convertește șirul într-un obiect DateTime valid, și întoarce acel DateTime:

    [IgnoreDataMember]
    public DateTime LastPush
    {
        get
        {
            return DateTime.ParseExact(JsonDate, „yyyy-MM-ddTHH:mm:ssZ”, CultureInfo.InvariantCulture);
        }
    }


    Haideți să trecem în revistă noile constructe de mai sus. Atributul IgnoreDataMember instruiește serializatorul că acest tip ar trebui să nu fie citit sau scris din/în niciun obiect JSON. Această proprietate conține doar un accesor get. Nu există un accesor set. Acesta este modul cum definiți o proprietate doar-pentru-citire în C#. (Da, dvs. puteți crea proprietăți doar-pentru-scriere în C#, dar valoarea lor este limitată.) Metda ParseExact(String, String, IFormatProvider) analizează un șir și creează un obiect DateTime folosind un format de dată furnizat, și adaugă metadate în plus la DateTime folosind un obiect CultureInfo. Dacă operația de analizare eșuează, accesorul proprietății aruncă o excepție.

    Pentru a folosi InvariantCulture, dvs. va trebui să adăugați namespace-ul System.Globalization la instrucțiunile using în repo.cs:

    using System.Globalization;

    La final, adăugați încă o instrucțiune de ieșire în consolă, și sunteți pregătit să construiți și să rulați această aplicație din nou:

    Console.WriteLine(repo.LastPush);

    Versiunea dvs. a trebui să se potrivească acum cu exemplul finalizat.

    Concluzie

    Acest tutorial v-a arătat cum să faceți cereri web, să analizați rezultatul, și să afișați proprietăți ale acestor rezultate. Dvs. ați adăugat de asemenea noi pachete ca dependențe în proiectul dvs. Ați văzut câteva din facilitățile limbajului C# care suportă tehnici orientate pe obiecte.

    Notă. Începând cu .NET Core 2.0, dvs. nu trebuie să rulați dotnet restore deoarece ea este rulată implicit de toate comenzile care cer o restaurare să aibă loc, cum sunt dotnet new, dotnet build și dotnet run. Este încă o comandă validă în unele scenarii în care a face o restaurare explicită are sens, cum ar fi construcții în integrare continuă în Azure DevOps Services sau în sisteme de construcție care cer să controleze explicit tipul la care restaurarea are loc.


    Tradus din această pagină oficială de documentație Microsoft.

    [C#] Tutorial „Aplicație de consolă”

    Acest tutorial vă învață câteva facilități în .NET Core și limbajul C#. Veți învăța:

    • Bazele Interfeței la linie de comandă .NET Core (CLI)
    • Structura unei Aplicații consolă C#
    • I/O Consolă
    • Bazele API-urilor de I/O Fișier în .NET
    • Bazele Programării asincrone bazate pe sarcină (Task) în .NET
    Veți construi o aplicație care citește un fișier text, și imită conținutul acestui fișier text în consolă. Ieșirea la consolă este setată să se potrivească cu ritmul citirii ei cu voce tare. Puteți grăbi sau încetini ritmul apăsând tastele ” (mai mare decât).
    Sunt o mulțime de facilități în acest tutorial. Haideți să le construim una câte una.

    Cerințe preliminare

    Va trebui să vă configurați mașina să ruleze .NET Core. Puteți găsi instrucțiunile de instalare pe pagina .NET Core. Puteți rula această aplicație pe Windows, Linux, maOS sau într-un container Docker. Va trebui să vă instalați editorul de cod favorit al dvs.

    Creați aplicația

    Primul pas este să creați o nouă aplicație. Deschideți o linie de comandă și creați un nou director pentru aplicația dvs. Făceți-l directorul curent. Tastați comanda dotnet new console în linia de comandă. Aceasta creează fișierele de începere pentru o aplicație de bază „Hello World”.
    Inainte de a începe s faceți modificări, haideți să mergem prin pașii rulării simplei aplicații Hello World. După crearea aplicației, tastați dotnet restore la linia de comandă. Această comandă rulează procesul de restaurare a pachetelor NuGet. NuGet este un organizator de pachete .NET. Această comandă descarcă toate dependențele care lipsesc pentru proiectul dvs. Fiindcă acesta este un proiect nou, nici una dintre dependențe nu este la locul ei, deci prima rulare va descărca framework-ul (cadrul de lucru) .NET Core. După acest pas inițial, va trebui să rulați dotnet restore doar când adăugați pachete de dependență noi, sau actualizați versiunile oricărei dependențe a dvs.
    Notă. Începând cu .NET Core 2.0, nu trebuie să rulați dotnet restore deoarece este rulat implicit de toate comenzile care cer o restaurare să aibă loc, cum sunt dotnet new, dotnet build și dotnet run. Este încă o comandă validă în unele scenarii în care facerea unei restaurări explicite are sens, cum ar fi construcții de integrare continuă în Azure DevOps Services sau în sisteme de construcție care necesită să controleze explicit timpul la care restaurarea are loc.
    După restaurarea pachetelor, rulați dotnet build. Aceasta execută motorul de construcție și creează executabilul aplicației dvs. In final, executați dotnet run pentru a rula aplicația dvs.
    Codul simplu al aplicației Hello World este în întregime în Program.cs. Deschideți acest fișier cu editorul de text favorit al dvs. Suntem pe cale să facem primele noastre schimbări. La începutul fișierului vedeți o instrucțiune using:
    using System;
    Această instrucțiune spune compilatorului că oricare tipuri din namespace-ul System sunt în scop. Ca alte limbaje orientate pe obiecte pe care poate le-ați folosit, C# folosește namespace-uri pentru a organiza tipuri. Acest program Hello World nu este diferit. Puteți vedea că programul este închis în namespace-ul cu numele bazat pe numele directorului curent. Pentru acest tutorial, haideți să schimbăm numele namespace-ului în TeleprompterConsole:
    namespace TeleprompterConsole

    Citirea și imitarea fișierului

    Prima facilitate de adăugat este abilitatea de a citi un fișier text și a afișa tot acel text în consolă. Mai întâi, haideți să adăugăm un fișier text. Copiați fișierul sampleQuotes.txt din depozitul GitHub pentru acest exemplu în directorul proiectului dvs. Acesta va servi ca scenariu pentru aplicația dvs. Dacă v-ar plăcea informații despre cum să descărcați aplicația exemplu pentru acest subiect, vedeți instrucțiunile în subiectul Samples and Tutorials.

    In continuare, adăugați următoarea metodă în clasa dvs. Program (chiar sub metoda Main):

    static IEnumerable ReadFrom(string file)
    {
        string line;
        using (var reader = File.OpenText(file))
        {
            while ((line = reader.ReadLine()) != null)
            {
                yield return line;
            }
        }
    }


    Această metodă folosește tipuri din două noi namespace-uri. Pentru ca aceasta să se compileze va trebui să adăugați următoarele două linii la începutul fișierului:

    using System.Collections.Generic;
    using System.IO;


    Interfața IEnumerable este definită în namespace-ul System.Collections.Generic. Clasa File este definită în namespace-ul System.IO.

    Această metodă este un tip special de metodă C# numită metodă iterator. Metodele enumeratoare întorc secvențe care sunt evaluate leneș. Aceasta înseamnă că fiecare element din secvență este generat atunci când este cerut de codul care consumă secvența. Metodele enumeratoare sunt metode care conțin una sau mai multe instrucțiuni yield return. Obiectul întors de metoda ReadFrom conține codul care generează fiecare element în secvență. In acest exemplu, aceasta implică citirea următorului rând de text din fișierul sursă, și întoarcerea acelui șir. De fiecare dată când codul apelant cere următorul element din secvență, codul citește următorul rând de text din fișier și îl întoarce. Când fișierul este citit complet, secvența indică faptul că nu mai există elemente.

    Există alte două elemente de sintaxă C# care ar putea fi noi pentru dvs. Instrucțiunea using din această metodă organizează curățarea resurselor. Variabila care este inițializată în instrucțiunea using (reader, în acest exemplu) trebuie să implementeze interfața IDisposable. Această interfață definește o singură metodă, Dispose, care ar trebui să fie apelată când resursa ar trebui să fie eliberată. Compilatorul generează acel apel când execuția ajunge la acolada închisă a instrucțiunii using. Codul generat de compilator se asigură că resursa este eliberată chiar dacă o excepție este aruncată din codul din blocul definit de instrucțiunea using.

    Variabila reader este definită folosind cuvântul cheie var. var definește o variabilă locală implicit tipizată. Aceasta înseamnă că tipul variabilei este determinat de tipul obiectului atribuit variabilei la momentul compilării. Aici, acesta este valoarea întoarsă de metoda OpenText(String), care este un obiect StreamReader.

    Acum, haideți să completăm codul pentru citirea fișierului în metoda Main:

    var lines = ReadFrom(„sampleQuotes.txt”);
    foreach (var line in lines)
    {
        Console.WriteLine(line);
    }


    Rulați programul (folosind dotnet run) și dvs. puteți vedea fiecare linie tipărită în consolă.

    Adăugarea întârzierilor și formatarea ieșirii

    Ceea ce aveți este afișat mult prea repede pentru a fi citit cu voce tare. Acum dvs. trebuie să adăugați întârzieri în ieșire. Începând, dvs. veți fi construind o parte din codul nucleu care permite procesarea asincronă. Totuși, acești primi pași vor urmă câteva anti-exemple. Anti-exemplele sunt indicate în comentarii după cum adăugați codul, și codul va fi actualizat în pași următori.

    Sunt doi pași în această secțiune. Mai întâi, veți actualiza metoda iterator să întoarcă cuvinte singure în loc de linii întregi. Aceea se face cu aceste modificări. Inlocuiți instrucțiunea yield return line; cu următorul cod:

    var words = line.Split(‘ ‘);
    foreach (var word in words)
    {
        yield return word + ” „;
    }
    yield return Environment.NewLine;


    In continuare, trebuie să modificați cum consumați liniile fișierului, și să adăugați o întârziere după scrierea fiecărui cuvânt. Inlocuiți instrucțiunea Console.WriteLine(line) în metoda Main cu următorul bloc:

    Console.Write(line);
    if (!string.IsNullOrWhiteSpace(line))
    {
        var pause = Task.Delay(200);
        // Synchronously waiting on a task is an
        // anti-pattern. This will get fixed in later
        // steps.
        pause.Wait();
    }


    Clasa Task este în namespace-ul System.Threading.Tasks, deci trebuie să adăugați acea instrucțiune using la începutul fișierului:

    using System.Threading.Tasks;

    Rulați exemplul, și verificați ieșirea. Acum, fiecare cuvânt singur este tipărit, urmat de o întârziere de 200 ms. Totuși, ieșirea afișată arată unele probleme deoarece fișierul de text sursă are câteva linii care au mai mult de 80 de caractere fără o rupere de linie. Aceasta poate fi greu de citit în timp ce se derulează. Este ușor de rezolvat. Doar veți urmări lungimea fiecărei linii, și veți genera o nouă linie de fiecare dată când lungimea liniei atinge o anumită limită. Declarați o variabilă locală după declararea words în metoda ReadFrom care reține lungimea liniei:

    var lineLength = 0;

    Apoi, adăugați următorul cod după instrucțiunea yield return word + ” „; (înainte de acolada închisă):

    lineLength += word.Length + 1;
    if (lineLength > 70)
    {
        yield return Environment.NewLine;
        lineLength = 0;
    }


    Rulați exemplul, și dvs. veți putea citi cu voce tare la ritmul lui preconfigurat.

    Sarcini asincrone

    In acest pas final, dvs. veți adăuga codul care scrie ieșirea asincron într-o sarcină, în același timp rulând o altă sarcină care citește intrare de la utilizator dacă dânsul/dânsa dorește să grăbească sau să încetinească afișarea textului. Acesta are câțiva pași în el și la sfârșit, veți avea toate actualizările de care aveți nevoie. Primul pas este să creați o metodă asincronă care întoarce Task, metodă care reprezintă codul pe care l-ați creat până acum să citească și să afișeze fișierul.

    Adăugați această metodă în clasa dvs. Program (ea este luată din corpul metodei dvs. Main):

    private static async Task ShowTeleprompter()
    {
        var words = ReadFrom(„sampleQuotes.txt”);
        foreach (var word in words)
        {
            Console.Write(word);
            if (!string.IsNullOrWhiteSpace(word))
            {
                await Task.Delay(200);
            }
        }
    }


    Veți observa două schimbări. Mai întâi, în corpul metodei, în loc de a apela Wait() pentru a aștepta în mod sincron pentru o sarcină să se finalizeze, această versiune folosește cuvântul cheie await. Pentru a face aceasta, dvs. trebuie să adăugați modificatorul async la semnătura metodei. Această metodă întoarce un Task. Observați că nu există nici o instrucțiune de întoarcere care întoarce un obiect Task. In loc, acel obiect Task este creat de codul generat de compilator când folosiți operatorul await. Vă puteți imagina că această metodă întoarce când atinge un await. Task-ul întors indică faptul că munca nu s-a terminat. Metoda își revine când sarcina așteptată se finalizează. Când s-a executat până la final, Task-ul întors indică faptul că este complet. Codul apelant poate monitoriza acel Task întors să determine când s-a finalizat.

    Puteți apela această nouă metodă în metoda dvs. Main:

    ShowTeleprompter().Wait();

    Aici, în Main, codul așteaptă în mod sincron. Ar trebui să folosiți operatorul await în loc de așteptarea sincronă ori de câte ori este posibil. Dar, în metoda Main a unei aplicații consolă, nu puteți folosi operatorul await. Aceasta ar rezulta în terminarea aplicației înainte ca toate sarcinile să fie finalizate.

    Notă. Dacă folosiți C# 7.1 sau mai târziu, puteți crea aplicații consolă cu metoda async_Main.

    In continuare, trebuie să scrieți următoarea metodă asincronă pentru a citi din Consolă și a urmări tastele ” (mai mare decât). Aici este metoda pe care o adăugați pentru această sarcină:

    private static async Task GetInput()
    {
        var delay = 200;
        Action work = () =>
        {
            do {
                var key = Console.ReadKey(true);
                if (key.KeyChar == ‘>’)
                {
                    delay -= 10;
                }
                else if (key.KeyChar == ‘<')
                {
                    delay += 10;
                }
            } while (true);
        };
        await Task.Run(work);
    }


    Aceasta creează o expresie lambda să reprezinte un delegat Action care citește o tastă din Consolă și modifică o variabilă locală reprezentând întârzierea când utilizatorul apasă tastele ” (mai mare decât). Această metodă folosește ReadKey() pentru a bloca și a aștepta după utilizator să apese o tastă.

    Pentru a finaliza această facilitate, dvs. trebuie să creați o nouă metodă întorcând async Task care începe ambele aceste sarcini (GetInput și ShowTeleprompter), și de asemenea organizează datele împărțite între aceste două sarcini.

    Este timpul să creați o clasă care poate mânui datele împărțite între aceste două sarcini. Această clasă conține două proprietăți publice: întârzierea, și un steag Done să indice că fișierul a fost citit în întregime:

    namespace TeleprompterConsole
    {
        internal class TelePrompterConfig
        {
            private object lockHandle = new object();
            public int DelayInMilliseconds { get; private set; } = 200;


            public void UpdateDelay(int increment) // negative to speed up
            {
                var newDelay = Min(DelayInMilliseconds + increment, 1000);
                newDelay = Max(newDelay, 20);
                lock (lockHandle)
                {
                    DelayInMilliseconds = newDelay;
                }
            }


            public bool Done { get; private set; }

            public void SetDone()
            {
                Done = true;
            }
        }
    }


    Puneți această clasă într-un fișier nu, și închideți această clasă în namespace-ul TeleprompterConsole cum s-a arătat deasupra. Dvs. va trebui de asemenea să adăugați o instrucțiune using static astfel încât dvs. puteți să referiți la metodele Min și Max fără clasa care le cuprinde sau nume de namespace-uri. O instrucțiune using static importă metodele dintr-o clasă. Aceasta este în contrast cu instrucțiunile using folosite până în acest punct care au importat toate clasele dintr-un namespace.

    using static System.Math;

    Cealaltă facilitate a limbajului care este nouă este instrucțiunea lock. Această instrucțiune asigură că doar un singur fir de execuție poate fi în acel cod în oricare moment dat. Dacă un fir de execuție este în secțiunea blocată, alte fire de execuție trebuie să aștepte după primul fir de execuție să iasă din acea secțiune. Instrucțiunea lock folosește un obiect care păzește secțiunea lock. Această clasă urmează un idiom standard să blocheze un obiect privat în clasă.

    In continuare, dvs. trebuie să actualizați metodele ShowTeleprompter și GetInput să folosească noul obiect config. Scrieți o finală metodă async care întoarce Task pentru a începe ambele sarcini și a ieși când prima se finalizează:

    private static async Task RunTeleprompter()
    {
        var config = new TelePrompterConfig();
        var displayTask = ShowTeleprompter(config);


        var speedTask = GetInput(config);
        await Task.WhenAny(displayTask, speedTask);
    }


    Metoda cea nouă aici este apelul WhenAny(Task[]). Aceasta creează un Task care se finalizează de îndată ce oricare din sarcinile din lista sa de argumente se finalizează.

    In continuare, dvs. va trebui să actualizați ambele metode ShowTeleprompter și GetInput să folosească obiectul config pentru întârziere:

    private static async Task ShowTeleprompter(TelePrompterConfig config)
    {
        var words = ReadFrom(„sampleQuotes.txt”);
        foreach (var word in words)
        {
            Console.Write(word);
            if (!string.IsNullOrWhiteSpace(word))
            {
                await Task.Delay(config.DelayInMilliseconds);
            }
        }
        config.SetDone();
    }


    private static async Task GetInput(TelePrompterConfig config)
    {
        Action work = () =>
        {
            do {
                var key = Console.ReadKey(true);
                if (key.KeyChar == ‘>’)
                    config.UpdateDelay(-10);
                else if (key.KeyChar == ‘<')
                    config.UpdateDelay(10);
            } while (!config.Done);
        };
        await Task.Run(work);
    }


    Această nouă versiune a ShowTeleprompter apelează o nouă metodă în clasa TeleprompterConfig. Acum, dvs. trebuie să actualizați Main să apeleze RunTeleprompter în loc de ShowTeleprompter:

    RunTeleprompter().Wait();

    Concluzie

    Acest tutorial v-a arătat câteva din facilitățile din jurul limbajului C# și al bibliotecilor .NET Core legate cu lucrul în aplicații Consolă. Dvs. puteți construi pe baza acestei cunoașteri pentru a explora mai mult despre limbaj, și clasele introduse aici. Ați văzut bazele I/O Fișier și Consolă, programarea asincronă bazată pe Task-uri care blochează și care nu blochează, un tur al limbajului C# și cum programele C# sunt organizate și interfața Linie de Comandă și uneltele .NET Core.

    Pentru mai multe informații despre I/O cu fișiere, vedeți subiectul File and Stream I/O. Pentru mai multe informații despre modelul de programare asincronă folosit în acest tutorial, vedeți subiectul Task-based Asynchronous Programming și subiectul Asynchronous programming.

    Tradus din această pagină oficială de documentație Microsoft.

    [C#] Pornire rapidă „Introducere în clase”

    Introducere în clase

    Această pornire rapidă așteaptă să aveți o mașină pe care o puteți folosi pentru dezvoltare. Subiectul .NET Get Started in 10 minutes are instrucțiuni pentru instalarea mediului dvs. de dezvoltare local pe Mac, PC sau Linux. O vedere rapidă de ansamblu a comenzilor pe care le veți folosi este în introducerea în pornirile rapide locale cu link-uri la mai multe detalii.

    Creați-vă aplicația

    Folosind o fereastră terminal, creați un director numit classes. Vă veți construi aplicația acolo. Schimbați la acel director și tastați dotnet new console în fereastra consolă. Această comandă creează aplicația dvs. Deschideți Program.cs. El ar trebui să arate astfel:

    using System;

    namespace classes
    {
        class Program
        {
            static void Main(string[] args)
            {
                Console.WriteLine(„Hello World!”);
            }
        }
    }

    În această pornire rapidă, urmează să creați noi tipuri care reprezintă un cont bancar. În mod normal dezvoltatorii definesc fiecare clasă într-un fișier text diferit. Aceasta face mai ușor să organizezi pe măsură ce un program crește în mărime. Creați un nou fișier numit BankAccount.cs în directorul classes.
    Acest fișier va conține definiția unui cont bancar. Programarea Orientată pe Obiecte organizează codul creând tipuri în formă de clase. Aceste clase conțin codul care reprezintă o entitate specifică. Clasa BankAccount reprezintă un cont bancar. Codul implementează operații specifice prin metode și proprietăți. În această pornire rapidă, contul bancar suportă acest comportament:

    1. Are un număr de 10 cifre care identifică în mod unic contul bancar.
    2. Are un șir de caractere care reține numele proprietarilor.
    3. Soldul poate fi obținut.
    4. Acceptă depuneri.
    5. Acceptă extrageri.
    6. Soldul inițial trebuie să fie pozitiv.
    7. Extragerile nu pot rezulta în sold negativ.

    Definiți tipul cont bancar

    Puteți începe creând bazele unei clase care definește acel comportament. Ar arăta astfel:

    using System;

    namespace classes
    {
        public class BankAccount
        {
            public string Number { get; }
            public string Owner { get; set; }
            public decimal Balance { get; }

            public void MakeDeposit(decimal amount, DateTime date, string note)
            {
            }

            public void MakeWithdrawal(decimal amount, DateTime date, string note)
            {
            }
        }
    }


    Înainte de a începe, haideți să aruncăm o privire la ce ați construit. Declararea namespace oferă o cale de a organiza logic codul dvs. Această pornire rapidă este relativ mică, deci veți pune tot codul într-un singur namespace.

    public class BankAccount definește clasa, sau tipul, pe care îl creați. Tot ce este între { și } care urmează declararea clasei definește comportamentul clasei. Există cinci membri ai clasei BankAccount. Primii trei sunt proprietăți. Proprietățile sunt elemente de date și pot avea cod care forțează validare sau alte reguli. Ultimii doi sunt metode. Metodele sunt blocuri de cod care realizează o singură funcție. Citind numele fiecăruia dintre membrii ar trebui să ofere destulă informație pentru dvs. sau alt dezvoltator să înțelegeți ce face clasa.

    Deschideți un nou cont

    Prima facilitate de implementat este deschiderea unui cont bancar. Când un client deschide un cont, ei trebuie să furnizeze un sold inițial, și informații despre deținătorul sau deținătorii acelui cont.

    Crearea unui nou obiect de tipul BankAccount înseamnă definirea unui constructor care atribuie aceste valori. Un constructor este un membru care are același nume ca al clasei. El este folosit pentru a inițializa obiecte ale acelui tip clasă. Adăugați următorul constructor la tipul BankAccount:

    public BankAccount(string name, decimal initialBalance)
    {
        this.Owner = name;
        this.Balance = initialBalance;
    }


    Constructorii sunt apelați când dvs. apelați un obiect folosind new. Înlocuiți linia Console.WriteLine(„Hello World!”); în program.cs cu următoarele linii (înlocuiți cu numele dvs.):

    var account = new BankAccount(„”, 1000);
    Console.WriteLine($”Account {account.Number} was created for {account.Owner} with {account.Balance} initial balance.”);


    Tastați dotnet run pentru a vedea ce are loc.

    Ați remarcat că numărul contului este gol? Este timpul să corectăm acest lucru. Numărul contului ar trebui să fie atribuit când obiectul este construit. Dar nu ar trebui să fie responsabilitatea apelantului să îl creeze. Codul clasei BankAccount ar trebui să știe cum să atribuie noi numere de cont. Un simplu mod de a face aceasta este să începeți cu un număr de 10 cifre. Incrementați-l când fiecare nou cont este creat. In final, memorați numărul contului curent când un obiect este construit.

    Adăugați următoarea declarație de membru la clasa BankAccount:

    private static int accountNumberSeed = 1234567890;

    Acesta este un membru de date. Este private, ceea ce înseamnă că poate fi accesat doar de codul dinăuntrul clasei BankAccount. Este o cale de a separa responsabilitățile publice (cum este a avea un număr de cont) de implementarea privată (cum numărul de cont este generat.) El este de asemenea static, ceea ce înseamnă că este împărțit de toate obiectele BankAccount. Valoarea unei variabile non-statice este unică la fiecare instanță a obiectului BankAccount. Adăugați următoarele două linii la constructor pentru a atribui numărul de cont:

    this.Number = accountNumberSeed.ToString();
    accountNumberSeed++;


    Tastați dotnet run pentru a vedea rezultatele.

    Creați depuneri și extrageri

    Clasa dvs. de cont bancar trebuie să accepte depuneri și extrageri pentru a lucra corect. Haideți să implementăm depuneri și extrageri creând un jurnal cu fiecare tranzacție pentru cont. Aceasta are câteva avantaje peste simpla actualizare a soldului la fiecare tranzacție. Istoricul poate fi folosid pentru a controla toate tranzacțiile și a lucra cu solduri zilnice. Calculând soldul din istoricul tuturor tranzacțiilor când este nevoie. orice erori într-o singură tranzacție care sunt corectate vor fi corect reflectate în soldul următoarei calculări.

    Haideți să începem creând un nou tip să reprezinte o tranzacție. Acesta este un simplu ti care nu are nici o responsabilitate. Ii trebuie câteva proprietăți. Creați un fișier nou numit Transaction.cs. Adăugați următorul cod în el:

    using System;

    namespace classes
    {
        public class Transaction
        {
            public decimal Amount { get; }
            public DateTime Date { get; }
            public string Notes { get; }


            public Transaction(decimal amount, DateTime date, string note)
            {
                this.Amount = amount;
                this.Date = date;
                this.Notes = note;
            }
        }
    }

    Acum, haideți să adăugăm o List de obiecte Transaction la clasa BankAccount. Adăugați următoarea declarație:

    private List allTransactions = new List();

    Clasa List vă cere să importați un namespace diferit. Adăugați următorul rând la începutul lui BankAccount.cs:

    using System.Collections.Generic;

    Acum, haideți să schimbăm cum Balance (soldul) este raportat. El poate fi găsit prin însumarea valorilor tuturor tranzacțiilor. Modificați declarația lui Balance în clasa BankAccount în următoarea:

    public decimal Balance
    {
        get
        {
            decimal balance = 0;
            foreach (var item in allTransactions)
            {
                balance += item.Amount;
            }

            return balance;
        }
    }


    Acest exemplu vă arată un aspect important al proprietăților. Dvs. calculați acum soldul când un alt programator cere valoarea. Calculul dvs. enumeră toate tranzacțiile, și oferă suma ca soldul curent.

    In continuare, implementați metodele MakeDeposit și MakeWithdrawal. Aceste metode vor impune finalele două reguli: că soldul inițial trebuie să fie pozitiv, și că orice extragere trebuie să nu creeze un sold negativ.

    Aceasta introduce conceptul de excepții. Calea standard de a indica faptul că o metodă nu poate să își finalizeze lucrul său cu succes este să arunce o excepție. Tipul excepției și mesajul asociat cu ea descrie eroarea. Aici, metoda MakeDeposit aruncă o excepție dacă sumă depunerii este negativă. Metoda MakeWithdrawal aruncă o excepție dacă suma extragerii este negativă, sau dacă aplicarea extragerii rezultă într-un sold negativ:

    public void MakeDeposit(decimal amount, DateTime date, string note)
    {
        if (amount <= 0)
        {
            throw new ArgumentOutOfRangeException(nameof(amount), „Amount of deposit must be positive”);
        }
        var deposit = new Transaction(amount, date, note);
        allTransactions.Add(deposit);
    }


    public void MakeWithdrawal(decimal amount, DateTime date, string note)
    {
        if (amount <= 0)
        {
            throw new ArgumentOutOfRangeException(nameof(amount), „Amount of withdrawal must be positive”);
        }
        if (Balance – amount < 0)
        {
            throw new InvalidOperationException(„Not sufficient funds for this withdrawal”);
        }
        var withdrawal = new Transaction(-amount, date, note);
        allTransactions.Add(withdrawal);
    }


    Instrucțiunea throw aruncă o excepție. Execuția metodei curente sfârșește, și va reporni când un bloc potrivit catch este găsit. Veți adăuga un bloc catch pentru a testa acest cod puțin mai încolo.

    Constructorul ar trebui să primească o schimbare astfel încât el să adauge o tranzacție inițială, în loc de a actualiza soldul direct. Fiindcă ați scris deja metoda MakeDeposit, apelați-o din constructorul dvs. Constructorul finalizat ar trebui să arate astfel:

    public BankAccount(string name, decimal initialBalance)
    {
        this.Number = accountNumberSeed.ToString();
        accountNumberSeed++;


        this.Owner = name;
        MakeDeposit(initialBalance, DateTime.Now, „Initial balance”);
    }


    DateTime.Now este o proprietate care întoarce data și ora curente. Testați aceasta adăugând câteva depuneri și extrageri în metoda dvs. Main:

    account.MakeWithdrawal(500, DateTime.Now, „Rent payment”);
    Console.WriteLine(account.Balance);
    account.MakeDeposit(100, DateTime.Now, „friend paid me back”);
    Console.WriteLine(account.Balance);


    Apoi, testați că dvs. prindeți condiții de eroare încercând să creați un cont cu un sold negativ:

    // Test that the initial balances must be positive:
    try
    {
        var invalidAccount = new BankAccount(„invalid”, -55);
    }
    catch (ArgumentOutOfRangeException e)
    {
        Console.WriteLine(„Exception caught creating account with negative balance”);
        Console.WriteLine(e.ToString());
    }


    Dvs. folosiți instrucțiunile try și catch pentru a marca un bloc de cod care poate arunca excepții, și pentru a prinde acele erori pe care le așteptați. Puteți folosi aceeași tehnică pentru a testa codul care aruncă pentru un sold negativ:

    // Test for a negative balance
    try
    {
        account.MakeWithdrawal(750, DateTime.Now, „Attempt to overdraw”);
    }
    catch (InvalidOperationException e)
    {
        Console.WriteLine(„Exception caught trying to overdraw”);
        Console.WriteLine(e.ToString());
    }


    Salvați fișierul și tastați dotnet run pentru a-l încerca.

    Provocare – înregistrați toate tranzacțiile

    Pentru a finaliza această pornire rapidă, puteți scrie metoda GetAccountHistory care creează un string pentru istoricul tranzacțiilor. Adăugați această metodă la tipul BankAccount:

    public string GetAccountHistory()
    {
        var report = new System.Text.StringBuilder();


        report.AppendLine(„Date\t\tAmount\tNote”);
        foreach (var item in allTransactions)
        {
            report.AppendLine($”{item.Date.ToShortDateString()}\t{item.Amount}\t{item.Notes}”);
        }


        return report.ToString();
    }


    Aceasta folosește clasa StringBuilder pentru a formata un șir care conține o linie pentru fiecare tranzacție. Ați văzut codul de formatare a șirurilor mai devreme în aceste porniri rapide. Un nou caracter este ‘\t’. Acesta introduce un tab pentru a formata ieșirea.

    Adăugați această linie pentru a o testa în Program.cs:

    Console.WriteLine(account.GetAccountHistory());

    Tastați dotnet run pentru a vedea rezultatele.

    Următorii pași

    Dacă v-ați blocat, puteți vedea sursa pentru această pornire rapidă în depozitul nostru GitHub.

    Felicitări, ați finalizat toate pornirile noastre rapide (Quickstarts). Dacă sunteți doritori să învățați mai mult, încercați tutorialele noastre.

    Tradus din această pagină oficială de documentație Microsoft.