これらの例では、 Func および Action ジェネリック デリゲートで共変性と反変性を使用して、メソッドの再利用を可能にし、コードの柔軟性を高める方法を示します。
共変性と反変性の詳細については、「 デリゲートの分散 (C#)」を参照してください。
共変型パラメーターでデリゲートを使用する
次の例は、ジェネリック Func デリゲートでの共分散サポートの利点を示しています。
FindByTitle メソッドは、String型のパラメーターを受け取り、Employee型のオブジェクトを返します。 ただし、Func<String, Person>はEmployeeを継承するため、このメソッドを 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;
}
}
反変型パラメーターでのデリゲートの使用
次の例は、ジェネリック Action デリゲートでの反変性サポートの利点を示しています。
AddToContacts メソッドは、Person型のパラメーターを受け取ります。 ただし、Action<Employee>はEmployeeを継承するため、このメソッドを 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;
}
}
反変性関数と匿名関数
匿名関数 (ラムダ式) を使用する場合、反変性に関連する直感に反する動作が発生する可能性があります。 次の例を確認してください。
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();
}
}
この動作は矛盾しているように見えます。反変性によって、基本型 (Person) を受け入れるデリゲートを派生型 (Employee) を受け入れるデリゲートを代入できる場合、ラムダ式の直接割り当てが失敗するのはなぜですか?
主な違いは 型推論です。 最初のケースでは、ラムダ式が最初に型 var を持つ変数に割り当てられます。これにより、コンパイラはラムダの型を Action<Person>として推論します。 デリゲートの反変性規則により、 Action<Employee> への後続の割り当てが成功します。
2 番目のケースでは、ラムダ式(Person p) => p.ReadContact()がAction<Employee>に割り当てられているときに型Action<Person>を持つ必要があることをコンパイラが直接推測することはできません。 匿名関数の型推論規則は、最初の型決定中に反変性を自動的に適用しません。
対処方法
直接割り当てを機能させるには、明示的なキャストを使用できます。
// 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();
この動作は、デリゲート反変性 (型が確立された後に機能します) とラムダ式の型推論 (コンパイル中に発生) の違いを示しています。
こちらも参照ください
.NET