이러한 예제는 Func 및 Action 제네릭 대리자에서 공변성 및 반공변성을 사용하여 메서드를 재사용하고 코드에서 더 많은 유연성을 제공하는 방법을 보여 줍니다.
공변 및 반공변에 대한 자세한 내용은 대리자(C#)의 변형을 참조하세요.
공변 형식 매개 변수와 대리자 사용
다음 예제에서는 제네릭 Func 대리자에서 공변성 지원의 이점을 보여 줍니다. 메서드는 FindByTitle 형식의 매개 변수를 받아 String 형식의 객체를 반환합니다. 그러나 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();
}
}
이 동작은 모순된 것처럼 보입니다. 반공변성으로 인해 기본 형식()을 허용하는 대리자를 파생 형식(PersonEmployee)을 예상하는 대리자 변수에 할당할 수 있는 경우 람다 식의 직접 할당이 실패하는 이유는 무엇인가요?
주요 차이점은 형식 유추입니다. 첫 번째 경우 람다 식은 먼저 형식 var이 있는 변수에 할당되므로 컴파일러가 람다의 형식을 .로 Action<Person>유추합니다. 대리자의 Action<Employee> 반공변성 규칙으로 인해 성공할 후속 할당입니다.
두 번째 경우 컴파일러는 람다 식 (Person p) => p.ReadContact() 이 할당될 때 형식 Action<Person> 이 있어야 한다고 직접 유추할 Action<Employee>수 없습니다. 익명 함수에 대한 형식 유추 규칙은 초기 형식 결정 중에 반공변성을 자동으로 적용하지 않습니다.
해결 방법
직접 할당 작업을 수행하려면 명시적 캐스팅을 사용할 수 있습니다.
// 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