对 Func 和 Action 泛型委托使用变体 - C#

对 Func 和 Action 泛型委托使用变体 - C#

这些示例演示如何在泛型委托Func和Action中使用协变和逆变,以便重复利用方法,并在代码中提供更大的灵活性。

有关协变和逆变的详细信息,请参阅委托中的变体 (C#)。

使用具有协变类型参数的委托

下例阐释了泛型 Func 委托中的协变支持的益处。

FindByTitle 方法接收一个 String 类型的参数,并返回一个 Employee 类型的对象。 但是,您可以将此方法分配给Func委托,因为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 findEmployee = FindByTitle;

// The delegate expects a method to return Person,

// but you can assign it a method that returns Employee.

Func 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继承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 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 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 employeeReadContact = personReadContact;

// This causes a compile error: CS1661.

// Action employeeReadContact2 = (Person p) => p.ReadContact();

}

}

这种行为似乎自相矛盾:如果逆变允许将接受基类型的委托(Person)分配给需要派生类型的委托变量(Employee),那么为什么直接分配一个 lambda 表达式会失败呢?

主要区别是 类型推理。 在第一种情况下,lambda 表达式首先分配给具有类型的 var变量,这会导致编译器将 lambda 的类型推断为 Action。 由于委托的逆变规则,后续对 Action 的分配成功。

第二种情况下,编译器无法直接推断 lambda 表达式(Person p) => p.ReadContact()在被Action分配到时应具有类型Action。 匿名函数的类型推理规则在初始类型确定期间不会自动应用逆变。

解决方法

若要实现直接赋值,可以使用显式强制转换:

// Explicit cast to the desired delegate type.

Action employeeReadContact = (Action)((Person p) => p.ReadContact());

// Or specify the lambda parameter type that matches the target delegate.

Action employeeReadContact2 = (Employee e) => e.ReadContact();

此行为说明了委托逆变(在类型确定后起作用)和 lambda 表达式类型推理(在编译期间发生)之间的差异。

另请参阅

协变和逆变 (C#)

泛型

相关推荐

吺怎么读

吺怎么读

11-02 💫 6504
对冲为什么要做,做什么,怎么做 - 博客
告别繁琐!Android互传,轻松实现跨设备文件快速共享
YY2080出来的几位斗鱼主播,周二珂曾叫纳妮,暗杠小发一曲走红

本文标签