MySQL Java JavaScript PHP Python HTML-CSS C-sharp

Moștenirea

Moștenirea (inheritance) este unul dintre punctele cheie ale programării orientate pe obiecte (OOP). Prin moștenire, o clasă poate prelua funcționalitățile unei alte clase.

Să presupunem că avem clasa Person, care descrie o persoană:

class Person {
   private string _name = "";

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

   public void Print() {
       Console.WriteLine(Name);
   }
}

Dar, la un moment dat, avem nevoie de o clasă care să descrie un angajat al unei companii - clasa Employee. Având în vedere că această clasă va implementa aceleași funcționalități ca și clasa Person, deoarece un angajat este, de asemenea, o persoană, ar fi rațional să facem clasa Employee o subclasă (sau moștenitor) a clasei Person, care, la rândul ei, este numită clasa de bază sau părinte (sau superclasă):

class Employee : Person {

}

După punctul dublu specificăm clasa de bază pentru clasa dată. Pentru clasa Employee, clasa de bază este Person, și prin urmare clasa Employee moștenește toate proprietățile, metodele și câmpurile care există în clasa Person. Singurul lucru care nu se transmite la moștenire sunt constructorii clasei de bază cu parametri.

Astfel, moștenirea implementează relația „este-un” (is-a), obiectul clasei Employee este de asemenea un obiect al clasei Person:

Person person = new Person { Name = "Tom" };
person.Print();   // Tom
person = new Employee { Name = "Sam" };
person.Print();   // Sam

Și, deoarece obiectul Employee este, de asemenea, un obiect al clasei Person, putem defini variabila astfel: Person p = new Employee().

În mod implicit, toate clasele sunt moștenite din clasa de bază Object, chiar dacă nu stabilim explicit moștenirea. Prin urmare, clasele definite mai sus, Person și Employee, pe lângă metodele lor proprii, vor avea și metodele clasei Object: ToString(), Equals(), GetHashCode() și GetType().

Toate clasele pot fi moștenite în mod implicit. Totuși, există unele restricții:

  • Nu se suportă moștenirea multiplă, o clasă poate moșteni doar de la o singură clasă
  • La crearea unei clase derivate trebuie să luăm în considerare tipul de acces al clasei de bază - tipul de acces al clasei derivate trebuie să fie același sau mai strict decât cel al clasei de bază. De exemplu, dacă clasa de bază are tipul de acces internal, atunci clasa derivată poate avea tipul de acces internal sau private, dar nu public

De asemenea, trebuie să luăm în considerare faptul că, dacă clasa de bază și clasa derivată se află în asamblări diferite, atunci clasa derivată poate moșteni doar de la o clasă care are modificatorul public.

  • Dacă o clasă este declarată cu modificatorul sealed, nu se poate moșteni acea clasă și nu se pot crea clase derivate. De exemplu, clasa următoare nu permite crearea de moștenitori:
sealed class Admin {
}
  • Nu se poate moșteni o clasă de la o clasă statică

Accesul la membrii clasei de bază din clasa derivată

Să ne întoarcem la clasele noastre Person și Employee. Deși Employee moștenește toată funcționalitatea clasei Person, să vedem ce se întâmplă în următorul caz:

class Employee : Person {
   public void PrintName() {
       Console.WriteLine(_name);
   }
}

Acest cod nu va funcționa și va genera o eroare, deoarece variabila _name este declarată cu modificatorul private și, prin urmare, doar clasa Person are acces la ea. Însă, în clasa Person este definită proprietatea publică Name, pe care o putem folosi, astfel încât următorul cod va funcționa corect:

class Employee : Person {
   public void PrintName() {
       Console.WriteLine(Name);
   }
}

Astfel, clasa derivată poate avea acces doar la acei membri ai clasei de bază care sunt definiți cu modificatorii private protected (dacă clasa de bază și clasa derivată se află în aceeași asamblare), public, internal (dacă clasa de bază și clasa derivată se află în aceeași asamblare), protected și protected internal.

Cuvântul cheie base

Acum să adăugăm constructori în clasele noastre:

class Person {
   public string Name { get; set; }
   public Person(string name) {
       Name = name;
   }
   public void Print() {
       Console.WriteLine(Name);
   }
}

class Employee : Person {
   public string Company { get; set; }
   public Employee(string name, string company)
       : base(name) {
       Company = company;
   }
}

Clasa Person are un constructor care setează proprietatea Name. Deoarece clasa Employee moștenește și setează aceeași proprietate Name, ar fi logic să nu rescriem codul de setare de mai multe ori, ci să apelăm codul corespunzător al clasei Person.

De asemenea, proprietățile care trebuie setate în constructorul clasei de bază și parametrii pot fi mult mai mulți.

Folosind cuvântul cheie base, putem accesa clasa de bază. În cazul nostru, în constructorul clasei Employee trebuie să setăm numele și compania.

Dar numele îl transmitem pentru setare în constructorul clasei de bază, adică în constructorul clasei Person, folosind expresia base(name).

Person person = new Person("Bob");
person.Print();     // Bob
Employee employee = new Employee("Tom", "Microsoft");
employee.Print();   // Tom

Constructorii în clasele derivate

Constructorii nu sunt transmiși clasei derivate prin moștenire. Dacă clasa de bază nu are un constructor implicit fără parametri, ci doar constructori cu parametri (cum este cazul clasei de bază Person), atunci clasa derivată trebuie să apeleze unul dintre acești constructori prin cuvântul cheie base.

De exemplu, dacă eliminăm definiția constructorului din clasa Employee, va apărea o eroare deoarece clasa Employee nu apelează constructorul clasei de bază. Chiar dacă am adăuga un constructor care să seteze aceleași proprietăți, tot am întâmpina o eroare:

class Employee : Person {
   public string Company { get; set; } = "";
}

În acest caz, vom primi o eroare pentru că clasa Employee nu corespunde clasei Person, adică nu apelează constructorul clasei de bază. Dacă am adăuga un constructor care să seteze aceleași proprietăți, tot am întâmpina o eroare:

class Employee : Person {
   public string Company { get; set; } = "";
   public Employee(string name, string company)    // Eroare!
   {
       Name = name;
       Company = company;
   }
}

Deci, în clasa Employee trebuie să apelăm explicit constructorul clasei Person prin cuvântul cheie base:

class Employee : Person {
   public string Company { get; set; } = "";
   public Employee(string name, string company)
       : base(name) {
       Company = company;
   }
}

Alternativ, am putea defini în clasa de bază un constructor fără parametri:

class Person {
   public string Name { get; set; }
   // Constructor fără parametri
   public Person() {
       Name = "Tom";
       Console.WriteLine("Apel constructor fără parametri");
   }


   public Person(string name) {
       Name = name;
   }
   public void Print() {
       Console.WriteLine(Name);
   }
}

Atunci, orice constructor din clasa derivată care nu apelează un constructor al clasei de bază ar apela implicit acest constructor implicit. De exemplu, următorul constructor:

public Employee(string company) {
   Company = company;
}

Ar fi echivalent cu următorul constructor:

public Employee(string company)
   : base() {
   Company = company;
}

Ordinea apelării constructorilor

Când se apelează un constructor al unei clase, mai întâi se execută constructorii claselor de bază și abia apoi cei ai claselor derivate. De exemplu, să luăm următoarele clase:

class Person {
   string name;
   int age;

   public Person(string name) {
       this.name = name;
       Console.WriteLine("Person(string name)");
   }
   public Person(string name, int age) : this(name) {
       this.age = age;
       Console.WriteLine("Person(string name, int age)");
   }
}
class Employee : Person {
   string company;

   public Employee(string name, int age, string company) : base(name, age) {
       this.company = company;
       Console.WriteLine("Employee(string name, int age, string company)");
   }
}

Când creăm un obiect Employee:

Employee tom = new Employee("Tom", 22, "Microsoft");

Vom obține următorul output la consolă:

Person(string name)
Person(string name, int age)
Employee(string name, int age, string company)

În final, obținem următoarea secvență de execuții:

  • La început, se apelează constructorul Employee(string name, int age, string company). Acesta deleghează execuția constructorului Person(string name, int age)
  • Se apelează constructorul Person(string name, int age), care la rândul său nu se execută imediat și trece execuția constructorului Person(string name)
  • Se apelează constructorul Person(string name), care trece execuția constructorului clasei System.Object, deoarece acesta este clasa de bază implicită pentru Person
  • Se execută constructorul System.Object.Object(), apoi execuția se întoarce constructorului Person(string name)
  • Se execută corpul constructorului Person(string name), apoi execuția se întoarce constructorului Person(string name, int age)
  • Se execută corpul constructorului Person(string name, int age), apoi execuția se întoarce constructorului Employee(string name, int age, string company)
  • Se execută corpul constructorului Employee(string name, int age, string company). În final, se creează obiectul Employee
← Lecția anterioară Lecția următoare →