这些示例演示如何在泛型委托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;
}
}
逆变和匿名函数
使用匿名函数(lambda 表达式)时,可能会遇到与逆变相关的反直觉行为。 请看下面的示例:
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
),那么为什么直接分配一个 lambda 表达式会失败呢?
主要区别是 类型推理。 在第一种情况下,lambda 表达式首先分配给具有类型的 var
变量,这会导致编译器将 lambda 的类型推断为 Action<Person>
。 由于委托的逆变规则,后续对 Action<Employee>
的分配成功。
第二种情况下,编译器无法直接推断 lambda 表达式(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();
此行为说明了委托逆变(在类型确定后起作用)和 lambda 表达式类型推理(在编译期间发生)之间的差异。