MySQL Java JavaScript PHP Python HTML-CSS C-sharp

Covarianță și contravarianță în delegați

Înțelegem cum funcționează rețelele neuronale și creăm una pentru căutarea știrilor.

Delegații pot fi covarianți și contravarianți. Covarianța unui delegat presupune că tipul returnat poate fi un tip derivat. Contravarianța unui delegat presupune că tipul parametrului poate fi un tip mai general.

Să analizăm covarianța și contravarianța pe exemplul următoarelor clase:

class Message
{
   public string Text { get; }
   public Message(string text) => Text = text;
   public virtual void Print() => Console.WriteLine($"Message: {Text}");
}
class EmailMessage : Message
{
   public EmailMessage(string text) : base(text) { }
   public override void Print() => Console.WriteLine($"Email: {Text}");
}
class SmsMessage : Message
{
   public SmsMessage(string text) : base(text) { }
   public override void Print() => Console.WriteLine($"Sms: {Text}");
}

În acest caz, clasa Message reprezintă un mesaj și definește proprietatea Text pentru stocarea textului mesajului, care este setat prin constructor. În metoda Print, mesajul este afișat pe consolă. Clasa EmailMessage reprezintă un mesaj email, iar SmsMessage - un mesaj SMS, și ambele clase sunt derivate din Message.

Covarianță

Covarianța permite atribuirea unui delegat unui metod care returnează un tip derivat din tipul returnat de delegat. Adică, dacă tipul returnat de delegat este Message, atunci metoda poate avea ca tip returnat clasa EmailMessage:

// delegatului cu tip de bază îi atribuim o metodă cu tip derivat
MessageBuilder messageBuilder = WriteEmailMessage; // covarianță
Message message = messageBuilder("Hello");
message.Print();    // Email: Hello

EmailMessage WriteEmailMessage(string text) => new EmailMessage(text);

delegate Message MessageBuilder(string text);

Aici delegatul MessageBuilder returnează un obiect Message. Cu toate acestea, datorită covarianței, acest delegat poate indica spre o metodă care returnează un obiect de tip derivat, de exemplu, metoda WriteEmailMessage.

Contravarianță

Contravarianța permite atribuirea unui delegat unei metode al cărei tip de parametru este mai general în raport cu tipul parametrului delegatului. De exemplu, să luăm clasele definite mai sus Message și EmailMessage și să le folosim în următorul exemplu:

// delegatului cu tip derivat îi atribuim o metodă cu tip de bază
EmailReceiver emailBox = ReceiveMessage; // contravarianță
emailBox(new EmailMessage("Welcome"));  // Email: Welcome

void ReceiveMessage(Message message) => message.Print();

delegate void EmailReceiver(EmailMessage message);

Deși delegatul primește ca parametru un obiect EmailMessage, îi putem atribui o metodă al cărui parametru este de tipul de bază Message. Ar putea părea, la prima vedere, că aici există o contradicție, adică utilizarea unui tip mai general în loc de unul derivat.

Cu toate acestea, în realitate, în delegat, la apelare, putem transmite doar obiecte de tip EmailMessage, iar orice obiect de tip EmailMessage este un obiect de tip Message, utilizat în metodă.

Covarianță și contravarianță în delegați generici

Delegații generici pot fi, de asemenea, covarianți și contravarianți, oferindu-ne mai multă flexibilitate în utilizarea lor.

Covarianță

De exemplu, să declarăm și să utilizăm un delegat generic covariant:

// returnează EmailMessage - tip mai specific
MessageBuilder<EmailMessage> EmailMessageWriter = (string text) => new EmailMessage(text);
// returnează tipul mai general Message
MessageBuilder<Message> messageBuilder = EmailMessageWriter;     // covarianță
Message message = messageBuilder("hello Tom"); // apelul delegatului
message.Print(); // Email: hello Tom

delegate T MessageBuilder<out T>(string text);

Datorită utilizării cuvântului cheie out, putem atribui delegatului de tip MessageBuilder<Message> (tip mai general) delegatul de tip MessageBuilder<EmailMessage> (tip mai specific).

Contravarianță

Să analizăm un delegat generic contravariant:

// primește un obiect de tip mai general
MessageReceiver<Message> messageReceiver = (Message message) => message.Print();
// primește un obiect de tip mai specific
MessageReceiver<EmailMessage> emailMessageReceiver = messageReceiver; // contravarianță

messageReceiver(new Message("Hello World!"));       // Message: Hello World!
messageReceiver(new EmailMessage("Hello World!"));  // Email: Hello World!

delegate void MessageReceiver<in T>(T message);

Utilizarea cuvântului cheie in permite atribuirea delegatului cu tip derivat (MessageReceiver<EmailMessage>) unui delegat cu tip de bază (MessageReceiver<Message>).

Ca și în cazul interfețelor generice, parametrul de tip covariant se aplică doar tipului de valoare returnat de delegat. Iar parametrul de tip contravariant se aplică doar parametrilor delegatului.

Adică, dacă generalizăm grosier, covarianța este de la un tip derivat la un tip mai general (EmailMessage -> Message), iar contravarianța este de la un tip mai general la un tip derivat (Message -> EmailMessage).

Combinarea covarianței și contravarianței

Un delegat poate folosi simultan ambii operatori: in și out. De exemplu:

MessageConverter<Message, EmailMessage> toEmailConverter = (Message message) => new EmailMessage(message.Text);

MessageConverter<SmsMessage, Message> converter = toEmailConverter;
Message message = converter(new SmsMessage("Hello work"));
message.Print();    // Email: Hello work

delegate E MessageConverter<in M, out E>(M message);

Aici delegatul MessageConverter reprezintă o acțiune condiționată, care convertește un obiect de tip M într-un tip E.

În program, este definită variabila converter, care reprezintă tipul MessageConverter<SmsMessage, Message> - adică un convertor din tipul SmsMessage în orice tip Message, grosier vorbind, convertește SMS-ul în orice alt tip de mesaj.

Această variabilă poate primi acțiunea - toEmailConverter, care creează un obiect de tip EmailMessage din orice tip de mesaj. Aici se aplică contravarianța: pentru parametru, în loc de tipul derivat SmsMessage, se folosește tipul de bază Message. Și de asemenea, există covarianță: în loc de tipul returnat Message se folosește tipul derivat EmailMessage.

← Lecția anterioară Lecția următoare →