共用方式為


建立 Variant 泛型介面 (C#)

您可以將介面中的泛型類型參數宣告為 covariant 或 contravariant。 協變 允許介面方法具有比泛型型別參數所定義更衍生的傳回型別。 Contravariance 可讓介面方法擁有參數類型,其不如泛型參數所指定的類型來得具衍生性。 具有 covariant 或 contravariant 泛型型別參數的泛型介面稱為 variant

備註

.NET Framework 4 引進了數個現有泛型介面的變異數支援。 如需 .NET 中變體介面的清單,請參閱 泛型介面中的變數 (C#)

宣告變異泛型介面

您可以使用 泛型型別參數的 inout 關鍵詞來宣告 Variant 泛型介面。

這很重要

refC# 中的 、 inout 參數不能是 variant。 實值型別也不支援變異數。

您可以使用 out 關鍵詞來宣告協變的泛型型別參數。 covariant 類型必須滿足下列條件:

  • 此類型只能當做介面方法的傳回型別使用,而不會當做方法自變數的類型使用。 下列範例會說明此範例,其中類型 R 宣告為covariant。

    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 關鍵詞來宣告泛型類型參數為逆變(contravariant)。 反變數類型只能當做方法自變數的類型使用,而不是做為介面方法的傳回型別。 反變數類型也可用於泛型條件約束。 下列程式代碼示範如何宣告反變數介面,並對其其中一個方法使用泛型條件約束。

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);
}

實作變異型泛型介面

您可以使用用於非變異介面的相同語法,在類別中實作 Variant 泛型介面。 下列程式代碼範例示範如何在泛型類別中實作covariant介面。

interface ICovariant<out R>
{
    R GetSomething();
}
class SampleImplementation<R> : ICovariant<R>
{
    public R GetSomething()
    {
        // Some code.
        return default(R);
    }
}

實作 Variant 介面的類別不可變。 例如,請考慮下列程序代碼。

// 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 泛型介面時,必須使用 inout 關鍵詞來明確指定衍生介面是否支援變異數。 編譯程式不會從正在擴充的介面推斷變異數。 例如,請考慮下列介面。

interface ICovariant<out T> { }
interface IInvariant<T> : ICovariant<T> { }
interface IExtCovariant<out T> : ICovariant<T> { }

在介面 IInvariant<T> 中,泛型型別參數是不變的,而在介面 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> { }

避免模棱兩可

當您實作 Variant 泛型介面時,變異數有時可能會導致模棱兩可。 應避免這種模棱兩可。

例如,如果您在一個類別中明確實作相同變體的泛型介面,但使用了不同的泛型型別參數,這可能會產生模糊。 在此情況下,編譯程式不會產生錯誤,但並未指定將在運行時間選擇哪一個介面實作。 這種模棱兩可會導致程序代碼中的細微錯誤。 請考慮下列程式碼範例。

// 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之間做選擇。 這可能會在您的程式代碼中造成問題。

另請參閱