共用方式為


使用差異性於 Func 和 Action 泛型委派 (C#)

這些範例示範如何在 和 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 運算式類型推斷(在編譯期間發生)。

另請參閱