Condividi tramite


Uso della varianza per i delegati generici func e action (C#)

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).

Vedere anche