Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
В этих примерах показано, как использовать ковариацию и контравариацию в Func
и Action
универсальных делегатах, чтобы включить повторное использование методов и обеспечить большую гибкость в коде.
Дополнительные сведения о ковариации и контравариации см. в статье "Вариативность делегатов (C#)."
Использование делегатов с ковариантными параметрами типа
В следующем примере показаны преимущества поддержки ковариации в обобщённых Func
делегатах. Метод FindByTitle
принимает параметр String
типа и возвращает объект Employee
типа. Однако этот метод можно назначить делегату Func<String, Person>
, так как Employee
наследует 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;
}
}
Использование делегатов с контравариантными параметрами типа
В следующем примере показаны преимущества поддержки контравариантности в универсальных Action
делегатах. Метод AddToContacts
принимает параметр Person
типа. Однако этот метод можно назначить делегату Action<Employee>
, так как Employee
наследует 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;
}
}
Контравариантность и анонимные функции
При работе с анонимными функциями (лямбда-выражениями) может возникнуть контринтуитивное поведение, связанное с контравариантностью. Рассмотрим следующий пример:
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();
}
}
Это поведение кажется противоречивым: если контравариантность позволяет назначать делегат, принимающий базовый тип (Person
), на переменную делегата, ожидающую производный тип (Employee
), то почему прямое назначение лямбда-выражения не удается?
Ключевое различие — вывод типов. В первом случае лямбда-выражение сначала присваивается переменной с типом var
, что заставляет компилятор определить тип лямбды как Action<Person>
. Последующее назначение Action<Employee>
успешно выполнено из-за правил контравариантности для делегатов.
Во втором случае компилятор не может напрямую определить, что лямбда-выражение (Person p) => p.ReadContact()
должно иметь тип Action<Person>
при присвоении Action<Employee>
. Правила вывода типов для анонимных функций автоматически не применяют контравариантность во время определения исходного типа.
Обходные пути
Для работы с прямым назначением можно использовать явное приведение:
// 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();
Это поведение иллюстрирует разницу между контравариантностью делегатов (которая работает после установки типов) и выводом типа лямбда-выражения (которое происходит во время компиляции).