MySQL Java JavaScript PHP Python HTML-CSS C-sharp

Restricții ale generics

Prin intermediul parametrilor universali, putem tipiza clasele generice cu orice tip. Totuși, uneori este necesar să specificăm tipul. De exemplu, avem următoarea clasă Message, care reprezintă un mesaj:

class Message
{
   public string Text { get; } // textul mesajului
   public Message(string text)
   {
       Text = text;
   }
}

Și, să presupunem că dorim să definim o metodă pentru a trimite mesaje sub formă de obiecte Message. La prima vedere, putem defini și folosi următoarea metodă:

SendMessage(new Message("Hello World"));

void SendMessage(Message message)
{
   Console.WriteLine($"Se trimite mesajul: {message.Text}");
}

Metoda SendMessage primește ca parametru un obiect Message și emulează trimiterea acestuia. Totul pare în regulă și nu pare să fie ceva mai bun de făcut. Totuși, clasa Message poate avea clase derivate. De exemplu, clasa EmailMessage pentru mesaje email și SmsMessage pentru mesaje SMS.

class EmailMessage : Message
{
   public EmailMessage(string text) : base(text) { }
}

class SmsMessage : Message
{
   public SmsMessage(string text) : base(text) { }
}

Ce se întâmplă dacă dorim să trimitem și mesaje care reprezintă aceste clase? Nu pare a fi o problemă, deoarece metoda SendMessage acceptă un obiect Message și, prin urmare, și obiecte ale claselor derivate:

SendMessage(new EmailMessage("Hello World"));

void SendMessage(Message message)
{
   Console.WriteLine($"Se trimite mesajul: {message.Text}");
}

Dar aici ne confruntăm cu conversia tipurilor: de la EmailMessage la Message. În plus, poate apărea o problemă de siguranță a tipurilor dacă dorim să convertim obiectul message în obiecte ale claselor derivate. În acest caz, pentru a evita conversiile, putem folosi genericele:

void SendMessage<T>(T message)
{
   Console.WriteLine($"Se trimite mesajul: {message.Text}"); // ! Eroare - proprietatea Text
}

Genericele permit evitarea conversiilor, dar acum ne confruntăm cu o altă problemă - parametrul universal T presupune orice tip. Dar nu orice tip are proprietatea Text.

Prin urmare, proprietatea Text pentru obiectul de tip T nu este definită și nu o putem folosi. Mai mult, pentru obiectul T implicit avem acces doar la metodele tipului object.

Astfel, apare problema: trebuie să evităm conversiile de tipuri și să folosim genericele, iar pe de altă parte, trebuie să accesăm funcționalitatea clasei Message în interiorul metodei. Restricțiile generice permit rezolvarea acestei probleme.

Restricțiile metodelor

Restricțiile metodelor sunt specificate după lista de parametri, după operatorul where:

numele_metodei<T>(parametri) where T: tip_restricție

După operatorul where este specificat parametrul universal pentru care se aplică restricția. Și prin intermediul operatorului : se indică tipul restricției - de obicei, o anumită clasă.

De exemplu, aplicăm restricții metodei SendMessage, care trimite obiecte de tip Message:

SendMessage(new Message("Hello World"));
SendMessage(new EmailMessage("Bye World"));

void SendMessage<T>(T message) where T: Message
{
   Console.WriteLine($"Se trimite mesajul: {message.Text}");
}

class Message
{
   public string Text { get; } // textul mesajului
   public Message(string text)
   {
       Text = text;
   }
}

class EmailMessage : Message
{
   public EmailMessage(string text) : base(text) { }
}

Expresia where T: Message în definiția metodei SendMessage spune că prin intermediul parametrului universal T vor fi transmise obiecte ale clasei Message și ale claselor derivate.

Astfel, compilatorul va ști că T va avea funcționalitatea clasei Message și, prin urmare, putem accesa metodele și proprietățile clasei Message în interiorul metodei fără probleme.

La apelarea metodei, nu este necesar să specificăm tipul în parantezele unghiulare - compilatorul va determina tipul pe baza valorii transmise:

SendMessage(new EmailMessage("Bye World"));

Totuși, se poate face acest lucru și explicit:

SendMessage<EmailMessage>(new EmailMessage("Bye World"));

Restricțiile generice în tipuri

În mod similar, putem defini și restricții pentru tipurile generice. De exemplu, restricțiile claselor generice:

class numele_clasei<T> where T: tip_restricție

Ca exemplu, definim o clasă pentru trimiterea mesajelor sub formă de obiecte Message:

class Messenger<T> where T : Message
{
   public void SendMessage(T message)
   {
       Console.WriteLine($"Se trimite mesajul: {message.Text}");
   }
}

class Message
{
   public string Text { get; } // textul mesajului
   public Message(string text)
   {
       Text = text;
   }
}

class EmailMessage : Message
{
   public EmailMessage(string text) : base(text) { }
}

Aici, pentru clasa Messenger este stabilită restricția where T : Message. Adică, în interiorul clasei Messenger, toate obiectele de tip T pot fi utilizate ca obiecte de tip Message. În acest caz, în clasa Messenger, metoda SendMessage emulează din nou trimiterea mesajelor.

Aplicăm clasa pentru trimiterea mesajelor:

Messenger<Message> telegram = new Messenger<Message>();
telegram.SendMessage(new Message("Hello World"));

Messenger<EmailMessage> outlook = new Messenger<EmailMessage>();
outlook.SendMessage(new EmailMessage("Bye World"));

Tipurile de restricții și restricțiile standard

Ca restricții, putem folosi următoarele tipuri:

  • Clase
  • Interfețe
  • class - parametrul universal trebuie să reprezinte o clasă
  • struct - parametrul universal trebuie să reprezinte o structură
  • new() - parametrul universal trebuie să reprezinte un tip care are un constructor public fără parametri

Există o serie de restricții standard pe care le putem folosi. În special, putem specifica o restricție pentru a se folosi doar structuri sau alte tipuri de valori:

class Messenger<T> where T : struct
{}

În acest caz, nu putem folosi restricții specifice structurilor, spre deosebire de clase.

De asemenea, putem seta ca restricție tipurile de referință:

class Messenger<T> where T : class
{}

De asemenea, putem specifica prin cuvântul new ca restricție clasele sau structurile care au un constructor public fără parametri:

class Messenger<T> where T : new()
{}

Dacă pentru un parametru universal sunt setate mai multe restricții, acestea trebuie să fie în ordine:

  • Numele clasei, class, struct. De asemenea, putem specifica doar una dintre aceste restricții
  • Numele interfeței
  • new()
class Smartphone<T> where T: Messenger, new()
{
 
}

Utilizarea mai multor parametri universali

Dacă o clasă folosește mai mulți parametri universali, putem specifica restricții pentru fiecare dintre aceștia:

class Messenger<T, P>
   where T : Message
   where P: Person
{
   public void SendMessage(P sender, P receiver, T message)
   {
       Console.WriteLine($"Expeditor: {sender.Name}");
       Console.WriteLine($"Destinatar: {receiver.Name}");
       Console.WriteLine($"Mesaj: {message.Text}");
   }
}

class Person
{
   public string Name { get;}
   public Person(string name) => Name = name;
}

class Message
{
   public string Text { get; } // textul mesajului
   public Message(string text) =>  Text = text;
}

În acest caz, pentru parametrul P vor fi transmise obiecte de tip Person, iar pentru parametrul T - obiecte de tip Message.

Aplicăm clasele:

Messenger<Message, Person> telegram = new Messenger<Message, Person>();
Person tom = new Person("Tom");
Person bob = new Person("Bob");
Message hello = new Message("Hello, Bob!");
telegram.SendMessage(tom, bob, hello);

Rezultatul afișat în consolă va fi:

Expeditor: Tom
Destinatar: Bob
Mesaj: Hello, Bob!
← Lecția anterioară Lecția următoare →