バリアント ジェネリック インターフェイスの作成 (C# および Visual Basic)
インターフェイスのジェネリック型パラメーターは、共変または反変として宣言できます。共変性により、インターフェイス メソッドの戻り値の型の派生を、ジェネリック型パラメーターで定義されている型よりも強くすることができます。反変性により、インターフェイス メソッドの引数の型の派生を、ジェネリック パラメーターで指定されている型よりも弱くすることができます。共変または反変のジェネリック型パラメーターを持つジェネリック インターフェイスは、バリアントと呼ばれます。
[!メモ]
.NET Framework 4 では、既存のいくつかのジェネリック インターフェイスに対して、変性のサポートが導入されています。.NET Framework のバリアント インターフェイスの一覧については、「ジェネリック インターフェイスの分散 (C# および Visual Basic)」を参照してください。
バリアント ジェネリック インターフェイスの宣言
バリアント ジェネリック インターフェイスは、ジェネリック型パラメーターの in キーワードと out キーワードを使用して宣言できます。
重要 |
---|
Visual Basic の ByRef パラメーター、および C# の ref パラメーターと out パラメーターは、バリアントにすることはできません。また、値型も変性をサポートしていません。 |
ジェネリック型パラメーターを共変として宣言するには、out キーワードを使用します。共変の型は、次の条件を満たす必要があります。
型がインターフェイス メソッドの戻り値の型としてのみ使用され、メソッド引数の型としては使用されない。この例を以下に示します。ここでは、型 R が共変として宣言されています。
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); }
この規則には例外が 1 つあります。メソッド パラメーターとして反変の汎用デリゲートを使用する場合は、このデリゲートのジェネリック型パラメーターとして共変の型を使用できます。この例を、以下の型 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 キーワードを使用します。反変の型は、メソッド引数の型としてのみ使用でき、インターフェイス メソッドの戻り値の型としては使用できません。また、反変の型はジェネリック制約にも使用できます。反変のインターフェイスを宣言し、そのメソッドの 1 つにジェネリック制約を使用する方法を次のコードに示します。
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();
}
次のコード例に示すように、同じインターフェイス内の異なる型パラメーターで、共変性と反変性の両方をサポートすることもできます。
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 では、デリゲート型を指定せずにバリアント インターフェイスのイベントを宣言することはできません。また、バリアント インターフェイスには、クラス、列挙型、および構造体を入れ子にすることはできませんが、インターフェイスを入れ子にすることは可能です。この例を次のコードに示します。
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
バリアント ジェネリック インターフェイスの実装
バリアント ジェネリック インターフェイスをクラスに実装する場合は、インバリアント インターフェイスに使用する構文と同じ構文を使用します。共変のインターフェイスをジェネリック クラスに実装する方法を次のコード例に示します。
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);
}
}
バリアント インターフェイスを実装するクラスは不変です。次に例を示します。
' 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;
バリアント ジェネリック インターフェイスの拡張
バリアント ジェネリック インターフェイスを拡張するときは、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)) の型パラメーターは共変ですが、どちらのインターフェイスでも同じインターフェイスを拡張します。これと同じ規則が、反変のジェネリック型パラメーターにも当てはまります。
拡張インターフェイスのジェネリック型パラメーター T が不変であれば、ジェネリック型パラメーター T が共変のインターフェイスと反変のインターフェイスの両方を拡張する 1 つのインターフェイスを作成できます。これを次のコード例に示します。
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 が共変として宣言されている場合は、それを拡張インターフェイスで反変として宣言することはできません。その逆についても同様です。これを次のコード例に示します。
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> { }
あいまいさの回避
バリアント ジェネリック インターフェイスの実装時には、変性によってあいまいさが発生することがあります。このような状況は回避する必要があります。
たとえば、同一のバリアント ジェネリック インターフェイスを、異なるジェネリック型パラメーターを使用して 1 つのクラスに明示的に実装すると、あいまいさが発生する可能性があります。この場合、コンパイラでエラーは生成されませんが、実行時にどのインターフェイスの実装が選択されるかは明確ではありません。これにより、特定しにくいバグが発生する可能性があります。次のコード例について考えます。
[!メモ]
Visual Basic で Option Strict Off を使用した場合、あいまいなインターフェイスの実装があると、コンパイラの警告が生成されます。Option Strict On を使用した場合は、コンパイラ エラーが発生します。
' 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)