インターフェイスでジェネリック型パラメーターを共変または反変として宣言できます。 共分散 により、インターフェイス メソッドはジェネリック型パラメーターで定義された戻り値よりも多くの派生戻り値型を持つことができます。 反変性 により、インターフェイス メソッドは、ジェネリック パラメーターで指定されたよりも派生度の低い引数型を持つことができます。 共変または反変のジェネリック型パラメーターを持つジェネリック インターフェイスは 、バリアント型と呼ばれます。
注
.NET Framework 4 では、複数の既存のジェネリック インターフェイスに差異のサポートが導入されました。 .NET のバリアント インターフェイスの一覧については、「 ジェネリック インターフェイスの分散 (C#)」を参照してください。
バリアント ジェネリック インターフェイスの宣言
ジェネリック型パラメーターの in
キーワードと out
キーワードを使用して、バリアント ジェネリック インターフェイスを宣言できます。
Von Bedeutung
ref
、 in
、および C# の out
パラメーターをバリアントにすることはできません。 値型も分散をサポートしていません。
out
キーワードを使用して、共変のジェネリック型パラメーターを宣言できます。 共変型は、次の条件を満たす必要があります。
型はインターフェイス メソッドの戻り値の型としてのみ使用され、メソッド引数の型として使用されません。 次の例では、
R
型が共変として宣言されています。interface ICovariant<out R> { R GetSomething(); // The following statement generates a compiler error. // void SetSomething(R sampleArg); }
この規則には 1 つの例外があります。 メソッド パラメーターとして反変ジェネリック デリゲートがある場合は、デリゲートのジェネリック型パラメーターとして型を使用できます。 これは、次の例で
R
型によって示されています。 詳細については、「 デリゲートの分散 (C#) 」および「 Func および Action Generic Delegate の分散の使用 (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
キーワードを使用して、ジェネリック型パラメーター反変を宣言できます。 反変型は、メソッド引数の型としてのみ使用でき、インターフェイス メソッドの戻り値の型として使用することはできません。 反変型は、ジェネリック制約にも使用できます。 次のコードは、反変インターフェイスを宣言し、そのメソッドの 1 つにジェネリック制約を使用する方法を示しています。
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;
バリアント ジェネリック インターフェイスの拡張
バリアント ジェネリック インターフェイスを拡張する場合は、 in
キーワードと out
キーワードを使用して、派生インターフェイスが分散をサポートするかどうかを明示的に指定する必要があります。 コンパイラは、拡張されるインターフェイスからの差異を推測しません。 たとえば、次のインターフェイスについて考えてみましょう。
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
が 1 つのインターフェイスで共変として宣言されている場合、拡張インターフェイスで反変を宣言することはできません。またはその逆も同様です。 これを次のコード例に示します。
interface ICovariant<out T> { }
// The following statement generates a compiler error.
// interface ICoContraVariant<in T> : ICovariant<T> { }
あいまいさを回避する
バリアント ジェネリック インターフェイスを実装すると、分散があいまいになることがあります。 このようなあいまいさを避ける必要があります。
たとえば、異なるジェネリック型パラメーターを持つ同じバリアント ジェネリック インターフェイスを 1 つのクラスに明示的に実装すると、あいまいさが生まれる可能性があります。 この場合、コンパイラはエラーを生成しませんが、実行時にどのインターフェイス実装を選択するかは指定されていません。 このあいまいさは、コードの微妙なバグにつながる可能性があります。 次のコード例について考えます。
// 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
を選択する方法は指定されていません。 これにより、コードに問題が発生する可能性があります。
こちらも参照ください
.NET