Nuta
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
W tych przykładach pokazano, jak używać kowariancji i kontrawariancji w Func i Action delegatach ogólnych, aby umożliwić ponowne użycie metod i zapewnić większą elastyczność w kodzie.
Aby uzyskać więcej informacji na temat kowariancji i kontrawariancji, zobacz Wariancja w delegatach (C#).
Używanie delegatów z kowariantnymi parametrami typu
W poniższym przykładzie przedstawiono zalety obsługi kowariancji w delegatach ogólnych Func . Metoda FindByTitle przyjmuje parametr String typu i zwraca obiekt Employee typu. Można jednak przypisać tę metodę do delegata Func<String, Person> , ponieważ Employee dziedziczy Person.
// Simple hierarchy of classes.
public class Person { }
public class Employee : Person { }
class Program
{
static Employee FindByTitle(String title)
{
// This is a stub for a method that returns
// an employee that has the specified title.
return new Employee();
}
static void Test()
{
// Create an instance of the delegate without using variance.
Func<String, Employee> findEmployee = FindByTitle;
// The delegate expects a method to return Person,
// but you can assign it a method that returns Employee.
Func<String, Person> findPerson = FindByTitle;
// You can also assign a delegate
// that returns a more derived type
// to a delegate that returns a less derived type.
findPerson = findEmployee;
}
}
Używanie delegatów z kontrawariantnymi parametrami typu
W poniższym przykładzie zilustrowano zalety wsparcia dla kontrawariancji w delegatach ogólnych Action. Metoda AddToContacts przyjmuje parametr Person typu . Można jednak przypisać tę metodę do delegata Action<Employee> , ponieważ Employee dziedziczy Person.
public class Person { }
public class Employee : Person { }
class Program
{
static void AddToContacts(Person person)
{
// This method adds a Person object
// to a contact list.
}
static void Test()
{
// Create an instance of the delegate without using variance.
Action<Person> addPersonToContacts = AddToContacts;
// The Action delegate expects
// a method that has an Employee parameter,
// but you can assign it a method that has a Person parameter
// because Employee derives from Person.
Action<Employee> addEmployeeToContacts = AddToContacts;
// You can also assign a delegate
// that accepts a less derived parameter to a delegate
// that accepts a more derived parameter.
addEmployeeToContacts = addPersonToContacts;
}
}
Kontrawariancja i funkcje anonimowe
Podczas pracy z funkcjami anonimowymi (wyrażenia lambda) może wystąpić sprzeczne zachowanie związane z kontrawariancją. Rozważmy następujący przykład:
public class Person
{
public virtual void ReadContact() { /*...*/ }
}
public class Employee : Person
{
public override void ReadContact() { /*...*/ }
}
class Program
{
private static void Main()
{
var personReadContact = (Person p) => p.ReadContact();
// This works - contravariance allows assignment.
Action<Employee> employeeReadContact = personReadContact;
// This causes a compile error: CS1661.
// Action<Employee> employeeReadContact2 = (Person p) => p.ReadContact();
}
}
To zachowanie wydaje się sprzeczne: jeśli kontrawariancja umożliwia przypisanie delegata, który akceptuje typ podstawowy (Person) do zmiennej delegowanej oczekiwanej typu pochodnego (Employee), dlaczego bezpośrednie przypisanie wyrażenia lambda kończy się niepowodzeniem?
Kluczową różnicą jest wnioskowanie typu. W pierwszym przypadku wyrażenie lambda jest najpierw przypisane do zmiennej o typie var, co powoduje, że kompilator wywnioskuje typ lambdy jako Action<Person>. Kolejne przypisanie do Action<Employee> powodzenia z powodu reguł kontrawariancji dla delegatów.
W drugim przypadku kompilator nie może bezpośrednio wywnioskować, że wyrażenie (Person p) => p.ReadContact() lambda powinno mieć typ Action<Person> , gdy jest przypisywany do Action<Employee>elementu . Reguły wnioskowania typów dla funkcji anonimowych nie są automatycznie stosowane kontrawariancji podczas początkowego określania typu.
Rozwiązania alternatywne
Aby wykonać bezpośrednie zadanie przypisania, możesz użyć jawnego rzutowania:
// Explicit cast to the desired delegate type.
Action<Employee> employeeReadContact = (Action<Person>)((Person p) => p.ReadContact());
// Or specify the lambda parameter type that matches the target delegate.
Action<Employee> employeeReadContact2 = (Employee e) => e.ReadContact();
To zachowanie ilustruje różnicę między kontrawariancją delegata (która działa po ustanowieniu typów) i wnioskowanie typu wyrażenia lambda (które występuje podczas kompilacji).