這些範例示範如何在 和 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<Employee>時應該有型Action<Person>別。 匿名函式的類型推斷規則不會在初始類型判斷期間自動套用反變數。
因應措施
若要進行直接指派工作,您可以使用明確轉型:
// 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 運算式類型推斷(在編譯期間發生)。