Compartilhar via


Usando variância para delegados genéricos Func e Action (C#)

Esses exemplos demonstram como usar covariância e contravariância nos delegados `Func` e `Action` genéricos para permitir a reutilização de métodos e fornecer mais flexibilidade em seu código.

Para obter mais informações sobre covariância e contravariância, consulte Variância em Delegados (C#).

Usando delegados com parâmetros de tipo covariante

O exemplo a seguir ilustra os benefícios do suporte à covariância nos delegados genéricos Func. O FindByTitle método usa um parâmetro do String tipo e retorna um objeto do Employee tipo. No entanto, você pode atribuir esse método ao Func<String, Person> delegado porque Employee herda 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;  
  
    }  
}  

Usando delegados com parâmetros de tipo contravariantes

O exemplo a seguir ilustra os benefícios do suporte à contravariância nos delegados genéricos Action. O AddToContacts método usa um parâmetro do Person tipo. No entanto, você pode atribuir esse método ao Action<Employee> delegado porque Employee herda 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;  
    }  
}  

Contravariância e funções anônimas

Ao trabalhar com funções anônimas (expressões lambda), você pode encontrar um comportamento contraintuitivo relacionado à contravariância. Considere o seguinte exemplo:

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

Esse comportamento parece contraditório: se a contravariância permite atribuir um delegado que aceita um tipo base (Person) a uma variável delegada que espera um tipo derivado (Employee), por que a atribuição direta da expressão lambda falha?

A principal diferença é a inferência de tipo. No primeiro caso, a expressão lambda é atribuída primeiro a uma variável com tipo var, o que faz com que o compilador infera o tipo lambda como Action<Person>. A atribuição subsequente será Action<Employee> bem-sucedida devido a regras de contravariância para delegados.

No segundo caso, o compilador não pode inferir diretamente que a expressão (Person p) => p.ReadContact() lambda deve ter tipo Action<Person> quando está sendo atribuída a Action<Employee>. As regras de inferência de tipo para funções anônimas não aplicam automaticamente contravariância durante a determinação de tipo inicial.

Soluções alternativas

Para fazer a atribuição direta funcionar, você pode usar a conversão explícita:

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

Esse comportamento ilustra a diferença entre a contravariância delegada (que funciona depois que os tipos são estabelecidos) e a inferência do tipo de expressão lambda (que ocorre durante a compilação).

Consulte também