Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Questi esempi illustrano come usare la covarianza e la controvarianza nei Func delegati generici e Action per consentire il riutilizzo dei metodi e offrire maggiore flessibilità nel codice.
Per altre informazioni sulla covarianza e sulla controvarianza, vedere Varianza nei delegati (C#).
Uso dei delegati con parametri di tipo covariante
L'esempio seguente illustra i vantaggi del supporto alla covarianza nei delegati generici Func. Il FindByTitle metodo accetta un parametro del String tipo e restituisce un oggetto del Employee tipo. Tuttavia, è possibile assegnare questo metodo al Func<String, Person> delegato perché Employee eredita 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;
}
}
Uso di delegati con parametri di tipo controvariante
L'esempio seguente illustra i vantaggi del supporto della controvarianza nei tipi di delegati generici Action. Il AddToContacts metodo accetta un parametro del Person tipo. Tuttavia, è possibile assegnare questo metodo al Action<Employee> delegato perché Employee eredita 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;
}
}
Controvarianza e funzioni anonime
Quando si utilizzano funzioni anonime (espressioni lambda), è possibile che si verifichi un comportamento controintuitivo correlato alla controvarianza. Si consideri l'esempio seguente:
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();
}
}
Questo comportamento sembra contraddittorio: se la controvarianza consente l'assegnazione di un delegato che accetta un tipo di base (Person) a una variabile delegato che prevede un tipo derivato (Employee), perché l'assegnazione diretta dell'espressione lambda ha esito negativo?
La differenza principale è l'inferenza del tipo. Nel primo caso, l'espressione lambda viene prima assegnata a una variabile con tipo var, che fa in modo che il compilatore deduca il tipo di lambda come Action<Person>. L'assegnazione successiva a Action<Employee> ha esito positivo a causa di regole di controvarianza per i delegati.
Nel secondo caso, il compilatore non può dedurre direttamente che l'espressione (Person p) => p.ReadContact() lambda deve avere un tipo Action<Person> quando viene assegnata a Action<Employee>. Le regole di inferenza del tipo per le funzioni anonime non applicano automaticamente la controvarianza durante la determinazione del tipo iniziale.
Soluzioni alternative
Per eseguire il lavoro di assegnazione diretta, è possibile usare il cast esplicito:
// 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();
Questo comportamento illustra la differenza tra la controvarianza del delegato (che funziona dopo la definizione dei tipi) e l'inferenza del tipo di espressione lambda (che si verifica durante la compilazione).