[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 < 1857 || 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 - 10 || 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.

Niciun comentariu:

Trimiteți un comentariu