Sdílet prostřednictvím


Použití rozptylu pro obecné delegáty Func a Action (C#)

Tyto příklady ukazují, jak používat kovarianci a kontravarianci v obecných delegátech Func a Action, abyste umožnili opakované použití metod a zajistili větší flexibilitu v kódu.

Další informace o kovarianci a kontravarianci naleznete v části Variance v delegátech (C#).

Použití delegátů s parametry kovariantního typu

Následující příklad ilustruje výhody podpory kovariance v obecných Func delegátech. Metoda FindByTitle přebírá parametr String typu a vrací objekt typu Employee . Tuto metodu však můžete přiřadit delegátu Func<String, Person> , protože Employee dědí 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;  
  
    }  
}  

Použití delegátů s parametry kontravariantního typu

Následující příklad ukazuje výhody podpory kontravariance u obecných Action delegátů. Metoda AddToContacts přebírá parametr typu Person . Tuto metodu však můžete přiřadit delegátu Action<Employee> , protože Employee dědí 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;  
    }  
}  

Kontravariance a anonymní funkce

Při práci s anonymními funkcemi (výrazy lambda) můžete narazit na neintuitivní chování související s kontravariancem. Podívejte se na následující příklad:

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();
    }
}

Zdá se, že toto chování je v rozporu: pokud kontravariance umožňuje přiřadit delegáta, který přijímá základní typ (Person) proměnné delegáta, který očekává odvozený typ (Employee), proč přímé přiřazení výrazu lambda selže?

Klíčovým rozdílem je odvození typu. V prvním případě je výraz lambda nejprve přiřazen k proměnné s typem var, což způsobí, že kompilátor odvodí typ lambda jako Action<Person>. Následné přiřazení bude Action<Employee> úspěšné kvůli pravidlům kontravariance pro delegáty.

V druhém případě kompilátor nemůže přímo odvodit, že výraz (Person p) => p.ReadContact() lambda by měl mít typAction<Person>, když je přiřazen .Action<Employee> Pravidla odvozování typů pro anonymní funkce automaticky nepoužívají kontravarianci při počátečním stanovení typu.

Alternativní řešení

Pokud chcete, aby přímé přiřazení fungovalo, můžete použít explicitní přetypování:

// 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();

Toto chování znázorňuje rozdíl mezi kontravariancem delegáta (který funguje po vytvoření typů) a odvození typu výrazu lambda (ke kterému dochází během kompilace).

Viz také