Variant 제네릭 인터페이스 만들기(C# 및 Visual Basic)

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

참고

.NET Framework 4에는 기존의 여러 제네릭 인터페이스에 대한 가변성(variance) 지원이 추가되었습니다. .NET Framework의 variant 인터페이스 목록은 제네릭 인터페이스의 가변성(C# 및 Visual Basic)을 참조하십시오.

variant 제네릭 인터페이스 선언

제네릭 형식 매개 변수의 in 및 out 키워드를 사용하여 variant 제네릭 인터페이스를 선언할 수 있습니다.

중요

Visual Basic의 ByRef 매개 변수와 C#의 ref 및 out 매개 변수는 variant일 수 없습니다. 또한 값 형식도 가변성(variance)을 지원하지 않습니다.

out 키워드를 사용하여 제네릭 형식 매개 변수를 공변(covariant)으로 선언할 수 있습니다. 공변(covariant) 형식은 다음 조건을 만족해야 합니다.

  • 형식이 인터페이스 메서드의 반환 형식으로만 사용되고 메서드 인수의 형식으로 사용되지 않는 경우. 이에 대한 예는 다음 예제에 나와 있는데, 여기서는 형식 R이 공변(covariant)으로 선언됩니다.

    Interface ICovariant(Of Out R)
        Function GetSomething() As R
        ' The following statement generates a compiler error.
        ' Sub SetSomething(ByVal sampleArg As R)
    End Interface
    
    interface ICovariant<out R>
    {
        R GetSomething();
        // The following statement generates a compiler error.
        // void SetSometing(R sampleArg);
    
    }
    

    그러나 이 규칙에는 한 가지 예외가 있습니다. 반공변(contravariant) 제네릭 대리자가 메서드 매개 변수인 경우에는 형식을 대리자에 대한 제네릭 형식 매개 변수로 사용할 수 있습니다. 이에 대한 예는 다음 예제에 형식 R로 나와 있습니다. 자세한 내용은 대리자의 가변성(C# 및 Visual Basic)Func 및 Action 제네릭 대리자에 가변성 사용(C# 및 Visual Basic)을 참조하십시오.

    Interface ICovariant(Of Out R)
        Sub DoSomething(ByVal callback As Action(Of R))
    End Interface
    
    interface ICovariant<out R>
    {
        void DoSomething(Action<R> callback);
    }
    
  • 형식이 인터페이스 메서드에 대한 제네릭 제약 조건으로 사용되지 않는 경우. 이에 대한 예는 다음 코드에 나와 있습니다.

    Interface ICovariant(Of Out R)
        ' The following statement generates a compiler error
        ' because you can use only contravariant or invariant types
        ' in generic contstraints.
        ' Sub DoSomething(Of T As R)()
    End Interface
    
    interface ICovariant<out R>
    {
        // The following statement generates a compiler error
        // because you can use only contravariant or invariant types
        // in generic contstraints.
        // void DoSomething<T>() where T : R;
    }
    

in 키워드를 사용하여 제네릭 형식 매개 변수를 반공변(contravariant)으로 선언할 수 있습니다. 반공변(contravariant) 형식은 메서드 인수의 형식으로만 사용할 수 있고 인터페이스 메서드의 반환 형식으로는 사용할 수 없습니다. 또한 반공변(contravariant) 형식은 제네릭 제약 조건에 사용할 수도 있습니다. 다음 코드에서는 반공변(contravariant) 인터페이스를 선언하고 해당 메서드 중 하나에 제네릭 제약 조건을 사용하는 방법을 보여 줍니다.

Interface IContravariant(Of In A)
    Sub SetSomething(ByVal sampleArg As A)
    Sub DoSomething(Of T As A)()
    ' The following statement generates a compiler error.
    ' Function GetSomething() As A
End Interface
interface IContravariant<in A>
{
    void SetSomething(A sampleArg);
    void DoSomething<T>() where T : A;
    // The following statement generates a compiler error.
    // A GetSomething();            
}

또한 다음 코드 예제와 같이 형식 매개 변수만 다른 동일한 인터페이스에서 공 분산 및 반공변성(Contravariance)을 둘 다 지원할 수도 있습니다.

Interface IVariant(Of Out R, In A)
    Function GetSomething() As R
    Sub SetSomething(ByVal sampleArg As A)
    Function GetSetSomething(ByVal sampleArg As A) As R
End Interface
interface IVariant<out R, in A>
{
    R GetSomething();
    void SetSomething(A sampleArg);
    R GetSetSometings(A sampleArg);
}

Visual Basic에서는 대리자 형식을 지정하지 않으면 variant 인터페이스에서 이벤트를 선언할 수 없습니다. 또한 variant 인터페이스에서는 중첩 클래스, 열거형 또는 구조체를 사용할 수 없고 중첩 인터페이스만 사용할 수 있습니다. 이에 대한 예는 다음 코드에 나와 있습니다.

Interface ICovariant(Of Out R)
    ' The following statement generates a compiler error.
    ' Event SampleEvent()
    ' The following statement specifies the delegate type and 
    ' does not generate an error.
    Event AnotherEvent As EventHandler

    ' The following statements generate compiler errors,
    ' because a variant interface cannot have
    ' nested enums, classes, or structures.

    'Enum SampleEnum : test : End Enum
    'Class SampleClass : End Class
    'Structure SampleStructure : Dim value As Integer : End Structure

    ' Variant interfaces can have nested interfaces.
    Interface INested : End Interface
End Interface

variant 제네릭 인터페이스 구현

variant 제네릭 인터페이스는 클래스에서 invariant 인터페이스에 사용된 것과 동일한 구문을 사용하여 구현합니다. 다음 코드 예제에서는 제네릭 클래스에서 공변(covariant) 인터페이스를 구현하는 방법을 보여 줍니다.

Interface ICovariant(Of Out R)
    Function GetSomething() As R
End Interface

Class SampleImplementation(Of R)
    Implements ICovariant(Of R)
    Public Function GetSomething() As R _
    Implements ICovariant(Of R).GetSomething
        ' Some code.
    End Function
End Class
interface ICovariant<out R>
{
    R GetSomething();
}
class SampleImplementation<R> : ICovariant<R>
{
    public R GetSomething()
    {
        // Some code.
        return default(R);
    }
}

variant 인터페이스를 구현하는 클래스는 invariant입니다. 예를 들어, 다음과 같은 코드를 생각해 볼 수 있습니다.

' The interface is covariant.
Dim ibutton As ICovariant(Of Button) =
    New SampleImplementation(Of Button)
Dim iobj As ICovariant(Of Object) = ibutton

' The class is invariant.
Dim button As SampleImplementation(Of Button) =
    New SampleImplementation(Of Button)
' The following statement generates a compiler error
' because classes are invariant.
' Dim obj As SampleImplementation(Of Object) = button
// 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;

variant 제네릭 인터페이스 확장

variant 제네릭 인터페이스를 확장할 때는 in 및 out 키워드를 사용하여 파생 인터페이스에서 가변성(variance)을 지원하는지 여부를 명시적으로 지정해야 합니다. 컴파일러에서는 확장되는 인터페이스에서 가변성(variance)을 유추하지 않습니다. 예를 들어, 다음과 같은 인터페이스를 생각해 볼 수 있습니다.

Interface ICovariant(Of Out T)
End Interface

Interface IInvariant(Of T)
    Inherits ICovariant(Of T)
End Interface

Interface IExtCovariant(Of Out T)
    Inherits ICovariant(Of T)
End Interface
interface ICovariant<out T> { }
interface IInvariant<T> : ICovariant<T> { }
interface IExtCovariant<out T> : ICovariant<T> { }

두 인터페이스가 동일한 인터페이스를 확장하는 경우에도 IInvariant<T>(Visual Basic의 경우 Invariant(Of T)) 인터페이스에서는 제네릭 형식 매개 변수 T가 invariant이고 IExtCovariant<out T>(Visual Basic의 경우 IExtCovariant (Of Out T)) 인터페이스에서는 이 형식 매개 변수가 공변(covariant)입니다. 이 규칙은 반공변(contravariant) 제네릭 형식 매개 변수에도 적용됩니다.

제네릭 형식 매개 변수 T가 공변(covariant)인 인터페이스와 확장 인터페이스에서 제네릭 형식 매개 변수 T가 invariant인 경우 이 형식 매개 변수가 반공변(contravariant)인 인터페이스를 둘 다 확장하는 인터페이스를 만들 수 있습니다. 이에 대한 예는 다음 코드 예제에 나와 있습니다.

Interface ICovariant(Of Out T)
End Interface

Interface IContravariant(Of In T)
End Interface

Interface IInvariant(Of T)
    Inherits ICovariant(Of T), IContravariant(Of T)
End Interface
interface ICovariant<out T> { }
interface IContravariant<in T> { }
interface IInvariant<T> : ICovariant<T>, IContravariant<T> { }

그러나 한 인터페이스에서 제네릭 형식 매개 변수 T가 공변(covariant)으로 선언된 경우에는 확장 인터페이스에서 이 형식 매개 변수를 반공변(contravariant)으로 선언할 수 없습니다. 이는 반대의 경우에도 마찬가지입니다. 이에 대한 예는 다음 코드 예제에 나와 있습니다.

Interface ICovariant(Of Out T)
End Interface

' The following statements generate a compiler error.
' Interface ICoContraVariant(Of In T)
'     Inherits ICovariant(Of T)
' End Interface
interface ICovariant<out T> { }
// The following statement generates a compiler error.
// interface ICoContraVariant<in T> : ICovariant<T> { }

모호성 방지

variant 제네릭 인터페이스를 구현하는 경우 가변성(variance) 때문에 모호성이 발생할 수 있는데, 이러한 경우가 발생해서는 안 됩니다.

예를 들어, 한 클래스에서 제네릭 형식 매개 변수만 다른 동일한 variant 제네릭 인터페이스를 구현하는 경우 모호성이 발생할 수 있습니다. 이 경우 컴파일러에서 오류가 발생하지는 않지만 런타임에 선택할 인터페이스 구현이 지정되지 않기 때문에 코드에서 버그가 발생할 수 있습니다. 다음 코드 예제를 참조하십시오.

참고

Option Strict Off를 사용하면 인터페이스 구현이 모호할 경우 Visual Basic에서 컴파일러 경고를 생성하고 Option Strict On를 사용하면 Visual Basic에서 컴파일러 오류를 생성합니다.

' Simple class hierarchy.
Class Animal
End Class

Class Cat
    Inherits Animal
End Class

Class Dog
    Inherits Animal
End Class

' This class introduces ambiguity
' because IEnumerable(Of Out T) is covariant.
Class Pets
    Implements IEnumerable(Of Cat), IEnumerable(Of Dog)

    Public Function GetEnumerator() As IEnumerator(Of Cat) _
        Implements IEnumerable(Of Cat).GetEnumerator
        Console.WriteLine("Cat")
        ' Some code.
    End Function

    Public Function GetEnumerator1() As IEnumerator(Of Dog) _
        Implements IEnumerable(Of Dog).GetEnumerator
        Console.WriteLine("Dog")
        ' Some code.
    End Function

    Public Function GetEnumerator2() As IEnumerator _
        Implements IEnumerable.GetEnumerator
        ' Some code.
    End Function
End Class

Sub Main()
    Dim pets As IEnumerable(Of Animal) = New Pets()
    pets.GetEnumerator()
End Sub
// 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 중에서 선택하는 방법이 지정되어 있지 않기 때문에 코드에서 문제가 발생할 수 있습니다.

참고 항목

참조

Func 및 Action 제네릭 대리자에 가변성 사용(C# 및 Visual Basic)

개념

제네릭 인터페이스의 가변성(C# 및 Visual Basic)