Dela via


Använda varians för Func och Action generiska delegeringar (C#)

Dessa exempel demonstrerar hur man använder samvarians och kontravarians i de generiska delegaterna Func och Action för att möjliggöra återanvändning av metoder och ge mer flexibilitet i din kod.

Mer information om kovarians och kontravarians finns i Varians i Delegater (C#).

Använda delegater med covarianta typparametrar

I följande exempel illustreras fördelarna med kovariansstöd i de generiska Func delegaterna. Metoden FindByTitle tar en parameter av typen String och returnerar ett objekt av typen Employee . Du kan dock tilldela den här metoden till ombudet Func<String, Person> eftersom Employee ärver 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;  
  
    }  
}  

Använda delegater med parametrar av typen Contravariant

I följande exempel illustreras fördelarna med kontravariansstöd i de generiska Action delegaterna. Metoden AddToContacts tar en parameter av typen Person . Du kan dock tilldela den här metoden till ombudet Action<Employee> eftersom Employee ärver 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;  
    }  
}  

Kontravarians och anonyma funktioner

När du arbetar med anonyma funktioner (lambda-uttryck) kan det uppstå kontraintuitivt beteende som rör kontravarians. Tänk på följande exempel:

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

Det här beteendet verkar motsägelsefullt: om kontravarians tillåter tilldelning av ett ombud som accepterar en bastyp (Person) till en delegatvariabel som förväntar sig en härledd typ (Employee), varför misslyckas direkttilldelningen av lambda-uttrycket?

Den viktigaste skillnaden är typinferens. I det första fallet tilldelas lambda-uttrycket först till en variabel med typen var, vilket gör att kompilatorn härleder lambda-typen som Action<Person>. Den efterföljande tilldelningen till Action<Employee> lyckas på grund av kontravariansregler för ombud.

I det andra fallet kan kompilatorn inte direkt dra slutsatsen att lambda-uttrycket (Person p) => p.ReadContact() ska ha typen Action<Person> när det tilldelas till Action<Employee>. Typinferensreglerna för anonyma funktioner tillämpar inte automatiskt kontravarians under den inledande typbestämningen.

Lösningar

Om du vill få direkttilldelning att fungera kan du använda explicit gjutning:

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

Det här beteendet illustrerar skillnaden mellan ombuds kontravarians (som fungerar efter att typer har upprättats) och lambda-uttryckstypsinferens (som inträffar under kompileringen).

Se även