您可以將介面中的泛型類型參數宣告為 covariant 或 contravariant。 協變 允許介面方法具有比泛型型別參數所定義更衍生的傳回型別。 Contravariance 可讓介面方法擁有參數類型,其不如泛型參數所指定的類型來得具衍生性。 具有 covariant 或 contravariant 泛型型別參數的泛型介面稱為 variant。
備註
.NET Framework 4 引進了數個現有泛型介面的變異數支援。 如需 .NET 中變體介面的清單,請參閱 泛型介面中的變數 (C#) 。
宣告變異泛型介面
您可以使用 泛型型別參數的 in 和 out 關鍵詞來宣告 Variant 泛型介面。
這很重要
refC# 中的 、 in和 out 參數不能是 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 泛型介面時,必須使用 in 和 out 關鍵詞來明確指定衍生介面是否支援變異數。 編譯程式不會從正在擴充的介面推斷變異數。 例如,請考慮下列介面。
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方法如何在Cat和Dog之間做選擇。 這可能會在您的程式代碼中造成問題。