共用方式為


建立 Variant 泛型介面 (C# 和 Visual Basic)

您可以將介面中的泛型型別參數宣告為 Covariant 和 Contravariant。 「共變數」(Covariance) 允許介面方法具有與泛型型別參數定義的傳回型別相比,其衍生程度較大的傳回型別。 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。 實值型別也不支援變異數。

您可以使用 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 泛型介面

您可以透過用於非變異 (Invariant) 介面的相同語法,在類別中實作 Variant 泛型介面。 在下列程式碼範例中,會示範如何在泛型類別中實作 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 介面的類別都是非變異的。 例如,試想下列程式碼。

' 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 關鍵字來明確指定衍生介面是否支援變異數。 編譯器不會從擴充中的介面推斷變異數。 例如,試想下列介面。

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 為非變異,而在 IExtCovariant<out T> (在 Visual Basic 中則為 IExtCovariant (Of Out T)) 中,型別參數為 Covariant,但是兩個介面都擴充同一個介面。 同樣的規則也適用於 Contravariant 泛型型別參數。

您可以建立介面,以擴充泛型型別參數 T 為 Covariant 的介面,以及若其泛型型別參數 T 在擴充介面中為非變異即為 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 泛型介面時,變異數有時候可能產生模稜兩可的情況。 您應該要設法避免這種情況。

例如,如果在一個類別中以不同的泛型型別參數明確實作相同的 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)