MySQL Java JavaScript PHP Python HTML-CSS C-sharp

Covarianța și contravarianța interfețelor generice

Noțiunile de covarianță și contravarianță sunt legate de posibilitatea utilizării unui tip diferit în locul unui alt tip, care se află mai sus sau mai jos în ierarhia moștenirii.

Există trei posibile moduri de comportament:

  • Covarianța: permite utilizarea unui tip mai specific decât cel definit inițial
  • Contravarianța: permite utilizarea unui tip mai general decât cel definit inițial
  • Invarianța: permite utilizarea doar a tipului definit

C# permite crearea interfețelor generice covariante și contravariante. Această funcționalitate crește flexibilitatea utilizării interfețelor generice în program. Implicit, toate interfețele generice sunt invariante.

Pentru a ilustra interfețele covariante și contravariante, să luăm următoarele clase:

class Message
{
   public string Text { get; set; }
   public Message(string text) => Text = text;
}
class EmailMessage : Message
{
   public EmailMessage(string text): base(text) { }
}

Aici este definită clasa Message, care primește textul prin constructor și îl stochează în proprietatea Text. Clasa EmailMessage reprezintă un email și doar apelează constructorul clasei de bază, transmițându-i textul mesajului.

Interfețe covariante

Interfețele generice pot fi covariante dacă parametrul generic folosește cuvântul cheie out. Un astfel de parametru trebuie să reprezinte tipul obiectului returnat de o metodă. De exemplu:

interface IMessenger<out T>
{
   T WriteMessage(string text);
}
class EmailMessenger : IMessenger<EmailMessage>
{
   public EmailMessage WriteMessage(string text)
   {
       return new EmailMessage($"Email: {text}");
   }
}

Aici, interfața generică IMessenger reprezintă o interfață de mesagerie și definește metoda WriteMessage() pentru a crea un mesaj. În momentul definirii interfeței, nu știm ce tip de obiect va fi returnat de această metodă. Cuvântul cheie out în definiția interfeței indică faptul că aceasta va fi covariantă.

Clasa EmailMessenger, care reprezintă o aplicație pentru trimiterea emailurilor, implementează această interfață și returnează un obiect EmailMessage din metoda WriteMessage().

Utilizare:

IMessenger<Message> outlook = new EmailMessenger();
Message message = outlook.WriteMessage("Hello World");
Console.WriteLine(message.Text);    // Email: Hello World

IMessenger<EmailMessage> emailClient = new EmailMessenger();
IMessenger<Message> messenger = emailClient;
Message emailMessage = messenger.WriteMessage("Hi!");
Console.WriteLine(emailMessage.Text);    // Email: Hi!

Putem atribui unui tip mai general, IMessenger<Message>, un obiect de tip mai specific, EmailMessenger sau IMessenger<EmailMessage>.

Dacă nu am folosi cuvântul cheie out:

interface IMessenger<T> { }

Am întâmpina o eroare la linia:

IMessenger<Message> outlook = new EmailMessenger();  // ! Eroare

IMessenger<EmailMessage> emailClient = new EmailMessenger();
IMessenger<Message> messenger = emailClient;  // ! Eroare

În acest caz, nu ar fi posibilă convertirea obiectului IMessenger<EmailMessage> la tipul IMessenger<Message>.

La crearea unei interfețe covariante, trebuie să luăm în considerare că parametrul generic poate fi folosit doar ca tipul valorii returnate de metodele interfeței. Nu poate fi folosit ca tip al argumentelor metodei sau în restricțiile metodelor interfeței.

Interfețe contravariant

Pentru a crea o interfață contravariantă, se folosește cuvântul cheie in. De exemplu, să folosim aceleași clase Message și EmailMessage și să definim următoarele tipuri:

interface IMessenger<in T>
{
   void SendMessage(T message);
}
class SimpleMessenger : IMessenger<Message>
{
   public void SendMessage(Message message)
   {
       Console.WriteLine($"Trimitere mesaj: {message.Text}");
   }
}

Aici, interfața IMessenger definește metoda SendMessage() pentru a trimite un mesaj. Cuvântul cheie in în definiția interfeței indică faptul că aceasta este contravariantă.

Clasa SimpleMessenger reprezintă o aplicație de trimitere a mesajelor și implementează această interfață, folosind tipul Message. Deci, SimpleMessenger reprezintă tipul IMessenger<Message>.

Utilizare:

IMessenger<EmailMessage> outlook = new SimpleMessenger();
outlook.SendMessage(new EmailMessage("Hi!"));

IMessenger<Message> telegram = new SimpleMessenger();
IMessenger<EmailMessage> emailClient = telegram;
emailClient.SendMessage(new EmailMessage("Hello"));

Deoarece interfața IMessenger folosește parametrul generic cu cuvântul cheie in, aceasta este contravariantă, astfel că putem atribui unei variabile de tip IMessenger<EmailMessage> un obiect de tip IMessenger<Message> sau SimpleMessenger.

Dacă nu s-ar folosi cuvântul cheie in, nu am putea face acest lucru. Un obiect de interfață cu un tip mai general poate fi convertit la un obiect de interfață cu un tip mai specific.

La crearea unei interfețe contravariante, trebuie să luăm în considerare că parametrul generic poate fi folosit doar ca tipul argumentelor metodei, dar nu și ca tipul valorii returnate de metodă.

Combinarea covarianței și contravarianței

Putem combina covarianța și contravarianța într-o singură interfață. De exemplu:

interface IMessenger<in T, out K>
{
   void SendMessage(T message);
   K WriteMessage(string text);
}
class SimpleMessenger : IMessenger<Message, EmailMessage>
{
   public void SendMessage(Message message)
   {
       Console.WriteLine($"Trimitere mesaj: {message.Text}");
   }
   public EmailMessage WriteMessage(string text)
   {
       return new EmailMessage($"Email: {text}");
   }
}

Practic, aici sunt combinate două exemple anterioare. Datorită covarianței/contravarianței, un obiect al clasei SimpleMessenger poate reprezenta tipurile IMessenger<EmailMessage, Message>, IMessenger<Message, EmailMessage>, IMessenger<Message, Message> și IMessenger<EmailMessage, EmailMessage>. Utilizare:

IMessenger<EmailMessage, Message> messenger = new SimpleMessenger();
Message message = messenger.WriteMessage("Hello World");
Console.WriteLine(message.Text);
messenger.SendMessage(new EmailMessage("Test"));

IMessenger<EmailMessage, EmailMessage> outlook = new SimpleMessenger();
EmailMessage emailMessage = outlook.WriteMessage("Mesaj din Outlook");
outlook.SendMessage(emailMessage);

IMessenger<Message, Message> telegram = new SimpleMessenger();
Message simpleMessage = telegram.WriteMessage("Mesaj din Telegram");
telegram.SendMessage(simpleMessage);
← Lecția anterioară Lecția următoare →