Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Estes exemplos demonstram como usar covariância e contravariância nos delegados genéricos Func e Action para permitir reutilização de métodos e fornecer mais flexibilidade no seu código.
Para obter mais informações sobre covariância e contravariância, consulte Variância de Delegados (C#).
Usando delegados com parâmetros de tipo covariante
O exemplo seguinte exemplifica 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 contravariante
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 esperando 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 é primeiramente atribuída a uma variável com type var, o que faz com que o compilador infera o tipo lambda como Action<Person>. A atribuição subsequente para ser bem-sucedida devido a Action<Employee> 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 a contravariância durante a determinação inicial do tipo.
Soluções
Para fazer com que a atribuição direta funcione, você pode usar a transmissã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 de tipo de expressão lambda (que ocorre durante a compilação).