다음을 통해 공유


C# 가변 제네릭 인터페이스 만들기

인터페이스에서 제네릭 형식 매개 변수를 공변 또는 반공변으로 선언할 수 있습니다. 공변성 에서는 인터페이스 메서드가 제네릭 형식 매개 변수에 정의된 것보다 더 많은 파생 반환 형식을 가질 수 있습니다. 반공변성(Contravariance )을 사용하면 인터페이스 메서드가 제네릭 매개 변수에 지정된 것보다 덜 파생된 인수 형식을 가질 수 있습니다. 공변 또는 반공변 제네릭 형식 매개 변수가 있는 제네릭 인터페이스를 variant라고 합니다.

비고

.NET Framework 4에는 여러 기존 제네릭 인터페이스에 대한 분산 지원이 도입되었습니다. .NET의 변형 인터페이스 목록은 제네릭 인터페이스의 분산(C#)을 참조하세요.

Variant 제네릭 인터페이스 선언

제네릭 형식 매개 변수에 대해 inout 키워드를 사용하여 변형 제네릭 인터페이스를 선언할 수 있습니다.

중요합니다

ref, inout 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;

변형 제네릭 인터페이스 확장

제네릭 변형 인터페이스를 확장할 때, 파생 인터페이스가 변동성을 지원하는지 여부를 명시적으로 지정하려면 inout 키워드를 사용해야 합니다. 컴파일러는 확장 중인 인터페이스의 분산을 유추하지 않습니다. 예를 들어 다음 인터페이스를 고려합니다.

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 메서드가 CatDog 중에서 어떻게 선택하는지 지정되지 않았습니다. 이로 인해 코드에 문제가 발생할 수 있습니다.

참고하십시오