인터페이스에서 제네릭 형식 매개 변수를 공변 또는 반공변으로 선언할 수 있습니다. 공변성 에서는 인터페이스 메서드가 제네릭 형식 매개 변수에 정의된 것보다 더 많은 파생 반환 형식을 가질 수 있습니다. 반공변성(Contravariance )을 사용하면 인터페이스 메서드가 제네릭 매개 변수에 지정된 것보다 덜 파생된 인수 형식을 가질 수 있습니다. 공변 또는 반공변 제네릭 형식 매개 변수가 있는 제네릭 인터페이스를 variant라고 합니다.
비고
.NET Framework 4에는 여러 기존 제네릭 인터페이스에 대한 분산 지원이 도입되었습니다. .NET의 변형 인터페이스 목록은 제네릭 인터페이스의 분산(C#)을 참조하세요.
Variant 제네릭 인터페이스 선언
제네릭 형식 매개 변수에 대해 in
및 out
키워드를 사용하여 변형 제네릭 인터페이스를 선언할 수 있습니다.
중요합니다
ref
, in
및 out
C#의 매개 변수는 variant일 수 없습니다. 값 형식도 분산을 지원하지 않습니다.
키워드를 사용하여 제네릭 형식 매개 변수 공변을 선언할 out
수 있습니다. 공변 형식은 다음 조건을 충족해야 합니다.
형식은 인터페이스 메서드의 반환 형식으로만 사용되며 메서드 인수의 형식으로 사용되지 않습니다. 다음은 형식
R
이 공변성으로 선언되는 다음 예제에 설명되어 있습니다.interface ICovariant<out R> { R GetSomething(); // The following statement generates a compiler error. // void SetSomething(R sampleArg); }
이 규칙에는 한 가지 예외가 있습니다. 메서드 매개 변수로 반공변 제네릭 대리자가 있는 경우 형식을 대리자의 제네릭 형식 매개 변수로 사용할 수 있습니다. 이는 다음 예제의 형식
R
에 의해 설명됩니다. 자세한 내용은 대리자의 분산(C#) 및 Func 및 Action 제네릭 대리자에 대한 분산 사용(C#)을 참조하세요.interface ICovariant<out R> { void DoSomething(Action<R> callback); }
형식은 인터페이스 메서드에 대한 제네릭 제약 조건으로 사용되지 않습니다. 다음 코드에 설명되어 있습니다.
interface ICovariant<out R> { // The following statement generates a compiler error // because you can use only contravariant or invariant types // in generic constraints. // void DoSomething<T>() where T : R; }
키워드를 사용하여 제네릭 형식 매개 변수 반공변을 선언할 in
수 있습니다. 반공변성 형식은 메서드 인수의 형식으로만 사용할 수 있으며 인터페이스 메서드의 반환 형식으로 사용할 수 없습니다. 반공변성 형식은 제네릭 제약 조건에도 사용할 수 있습니다. 다음 코드에서는 반공변 인터페이스를 선언하고 해당 메서드 중 하나에 대해 제네릭 제약 조건을 사용하는 방법을 보여 줍니다.
interface IContravariant<in A>
{
void SetSomething(A sampleArg);
void DoSomething<T>() where T : A;
// The following statement generates a compiler error.
// A GetSomething();
}
다음 코드 예제와 같이 동일한 인터페이스에서 공변성 및 반공변성 모두를 지원할 수 있지만 다른 형식 매개 변수에 대해서도 지원할 수 있습니다.
interface IVariant<out R, in A>
{
R GetSomething();
void SetSomething(A sampleArg);
R GetSetSomethings(A sampleArg);
}
변형 제네릭 인터페이스 구현
고정 인터페이스에 사용되는 것과 동일한 구문을 사용하여 클래스에서 변형 제네릭 인터페이스를 구현합니다. 다음 코드 예제에서는 제네릭 클래스에서 공변 인터페이스를 구현하는 방법을 보여줍니다.
interface ICovariant<out R>
{
R GetSomething();
}
class SampleImplementation<R> : ICovariant<R>
{
public R GetSomething()
{
// Some code.
return default(R);
}
}
변형 인터페이스를 구현하는 클래스는 고정되어 있습니다. 예를 들어 다음 코드를 고려합니다.
// The interface is covariant.
ICovariant<Button> ibutton = new SampleImplementation<Button>();
ICovariant<Object> iobj = ibutton;
// The class is invariant.
SampleImplementation<Button> button = new SampleImplementation<Button>();
// The following statement generates a compiler error
// because classes are invariant.
// SampleImplementation<Object> obj = button;
변형 제네릭 인터페이스 확장
제네릭 변형 인터페이스를 확장할 때, 파생 인터페이스가 변동성을 지원하는지 여부를 명시적으로 지정하려면 in
및 out
키워드를 사용해야 합니다. 컴파일러는 확장 중인 인터페이스의 분산을 유추하지 않습니다. 예를 들어 다음 인터페이스를 고려합니다.
interface ICovariant<out T> { }
interface IInvariant<T> : ICovariant<T> { }
interface IExtCovariant<out T> : ICovariant<T> { }
IInvariant<T>
인터페이스에서 제네릭 형식 매개 변수 T
는 고정된 반면 IExtCovariant<out T>
형식 매개 변수는 공변이지만 두 인터페이스는 동일한 인터페이스를 확장합니다. 반공변 제네릭 형식 매개 변수에 동일한 규칙이 적용됩니다.
제네릭 형식 매개 변수 T
가 확장 인터페이스에서 불변일 경우, 제네릭 형식 매개 변수 T
가 공변인 인터페이스와 반공변인 인터페이스를 모두 확장하는 인터페이스를 만들 수 있습니다. 다음 코드 예제에 설명되어 있습니다.
interface ICovariant<out T> { }
interface IContravariant<in T> { }
interface IInvariant<T> : ICovariant<T>, IContravariant<T> { }
그러나 제네릭 형식 매개 변수 T
가 한 인터페이스에서 공변성으로 선언된 경우 확장 인터페이스에서 반공변성으로 선언하거나 그 반대로 선언할 수 없습니다. 다음 코드 예제에 설명되어 있습니다.
interface ICovariant<out T> { }
// The following statement generates a compiler error.
// interface ICoContraVariant<in T> : ICovariant<T> { }
모호성 방지
변형 제네릭 인터페이스를 구현할 때 분산으로 인해 모호성이 발생할 수 있습니다. 이러한 모호성을 피해야 합니다.
예를 들어 한 클래스에서 서로 다른 제네릭 형식 매개 변수를 사용하여 동일한 변형 제네릭 인터페이스를 명시적으로 구현하는 경우 모호성을 만들 수 있습니다. 이 경우 컴파일러는 오류를 생성하지 않지만 런타임에 어떤 인터페이스 구현을 선택할지 지정되지 않았습니다. 이러한 모호성으로 인해 코드에서 미묘한 버그가 발생할 수 있습니다. 다음 코드 예제를 생각해보세요.
// Simple class hierarchy.
class Animal { }
class Cat : Animal { }
class Dog : Animal { }
// This class introduces ambiguity
// because IEnumerable<out T> is covariant.
class Pets : IEnumerable<Cat>, IEnumerable<Dog>
{
IEnumerator<Cat> IEnumerable<Cat>.GetEnumerator()
{
Console.WriteLine("Cat");
// Some code.
return null;
}
IEnumerator IEnumerable.GetEnumerator()
{
// Some code.
return null;
}
IEnumerator<Dog> IEnumerable<Dog>.GetEnumerator()
{
Console.WriteLine("Dog");
// Some code.
return null;
}
}
class Program
{
public static void Test()
{
IEnumerable<Animal> pets = new Pets();
pets.GetEnumerator();
}
}
이 예제에서는 pets.GetEnumerator
메서드가 Cat
과 Dog
중에서 어떻게 선택하는지 지정되지 않았습니다. 이로 인해 코드에 문제가 발생할 수 있습니다.
참고하십시오
.NET