Erstellen varianter generischer Schnittstellen (C# und Visual Basic)
Sie können generische Typparameter in Schnittstellen als Kovariante oder als Kontravariante deklarieren. Kovarianz ermöglicht Schnittstellenmethoden, stärker abgeleitete Rückgabetypen zu verwenden, als durch die generischen Typparameter definiert werden. Kontravarianz ermöglicht Schnittstellenmethoden, weniger stark abgeleitete Argumenttypen zu verwenden, als durch die generischen Parameter angegeben werden. Eine generische Schnittstelle mit ko- oder kontravarianten generischen Typparametern wird als variant bezeichnet.
Hinweis
In .NET Framework 4 wird die Varianzunterstützung für mehrere vorhandene generische Schnittstellen eingeführt.Eine Liste der varianten Schnittstellen in .NET Framework finden Sie unter Varianz in generischen Schnittstellen (C# und Visual Basic).
Deklarieren von varianten generischen Schnittstellen
Sie können variante generische Schnittstellen deklarieren, indem Sie das in-Schlüsselwort und das out-Schlüsselwort für generische Typparameter verwenden.
Wichtig
ByRef-Parameter in Visual Basic sowie ref- und out-Parameter in C# können nicht variant sein.Auch Werttypen unterstützen keine Varianz.
Mit dem out-Schlüsselwort können Sie einen generischen Typparameter als Kovariante deklarieren. Der kovariante Typ muss die folgenden Bedingungen erfüllen:
Der Typ wird nur als Rückgabetyp von Schnittstellenmethoden und nicht als Typ von Methodenargumenten verwendet. Dies wird im folgenden Beispiel veranschaulicht, in dem der Typ R als Kovariante deklariert wird.
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); }
Es gibt allerdings eine Ausnahme zu dieser Regel. Wenn Sie einen kontravarianten generischen Delegaten als Methodenparameter verwenden, können Sie den Typ als generischen Typparameter für den Delegaten verwenden. Dies wird durch den Typ R im folgenden Beispiel veranschaulicht. Weitere Informationen finden Sie unter Varianz in Delegaten (C# und Visual Basic) und unter Verwenden von Varianz für die generischen Delegaten Func und Action (C# und 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); }
Der Typ wird nicht als generische Einschränkung für die Schnittstellenmethoden verwendet. Dies wird durch folgenden Code veranschaulicht:
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; }
Mit dem in-Schlüsselwort können Sie einen generischen Typparameter als Kontravariante deklarieren. Der kontravariante Typ kann nur für Methodenargumente und nicht als Rückgabetyp von Schnittstellenmethoden verwendet werden. Der kontravariante Typ kann auch für generische Einschränkungen verwendet werden. Im folgenden Code wird veranschaulicht, wie eine kontravariante Schnittstelle deklariert und eine generische Einschränkung für eine ihrer Methoden verwendet wird.
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();
}
Bei unterschiedlichen Typparametern ist jedoch auch die Verwendung verschiedener Ko- und Kontravarianzen in der gleichen Schnittstelle möglich, wie im folgenden Codebeispiel veranschaulicht wird.
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);
}
In Visual Basic können Sie keine Ereignisse in varianten Schnittstellen deklarieren, ohne den Delegattyp anzugeben. Variante Schnittstellen können zwar geschachtelte Schnittstellen, jedoch keine geschachtelten Klassen, Enumerationen oder Strukturen aufweisen. Dies wird durch folgenden Code veranschaulicht:
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
Implementieren von varianten generischen Schnittstellen
Variante generische Schnittstellen werden mit der gleichen Syntax in Klassen implementiert, die für invariante Schnittstellen verwendet wird. Im folgenden Codebeispiel wird gezeigt, wie eine kovariante Schnittstelle in einer generischen Klasse implementiert wird.
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);
}
}
Klassen, die variante Schnittstellen implementieren, sind nicht variant. Betrachten Sie hierzu den folgenden Beispielcode:
' 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;
Erweitern von varianten generischen Schnittstellen
Wenn Sie eine variante generische Schnittstelle erweitern, müssen Sie mit dem in-Schlüsselwort und dem out-Schlüsselwort explizit angeben, ob Varianz von der abgeleiteten Schnittstelle unterstützt wird. Der Compiler leitet eine mögliche Varianz nicht von der Schnittstelle ab, die erweitert wird. Betrachten Sie beispielsweise die folgenden Schnittstellen:
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> { }
Der generische Typparameter T in der IInvariant<T>-Schnittstelle (Invariant(Of T) in Visual Basic) ist nicht variant. Der Typparameter in der IExtCovariant<out T>-Schnittstelle (IExtCovariant (Of Out T) in Visual Basic) hingegen ist kovariant, obwohl beide Schnittstellen die gleiche Schnittstelle erweitern. Die gleiche Regel gilt für kontravariante generische Typparameter.
Sie können eine Schnittstelle erstellen, die sowohl die Schnittstelle mit dem kovarianten generischen Typparameter T als auch die Schnittstelle mit dem kontravarianten Typparameter implementiert, wenn der generische Typparameter T in der erweiternden Schnittstelle nicht variant ist. Dies wird im folgenden Codebeispiel gezeigt.
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> { }
Wenn der generische Typparameter T jedoch in einer Schnittstelle als Kovariante deklariert wird, können Sie ihn nicht in der erweiternden Schnittstelle als Kontravariante deklarieren und umgekehrt. Dies wird im folgenden Codebeispiel gezeigt.
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> { }
Vermeiden von Mehrdeutigkeit
Wenn Sie variante generische Schnittstellen implementieren, kann Varianz manchmal zu Mehrdeutigkeit führen. Dies sollte vermieden werden.
Wenn Sie z. B. die gleiche variante generische Schnittstelle explizit mit unterschiedlichen generischen Typparametern in einer Klasse implementieren, kann dies zu Mehrdeutigkeit führen. Der Compiler erzeugt in diesem Fall keinen Fehler. Es wird jedoch nicht angegeben, welche Schnittstellenimplementierung zur Laufzeit ausgewählt wird. Dies kann zu schwer erkennbaren Fehlern im Code führen. Betrachten Sie folgendes Codebeispiel.
Hinweis
Bei Option Strict Off generiert Visual Basic eine Compilerwarnung, wenn eine mehrdeutige Schnittstellenimplementierung vorliegt.Bei Option Strict On generiert Visual Basic einen Compilerfehler.
' 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();
}
}
In diesem Beispiel ist nicht angegeben, wie die pets.GetEnumerator-Methode zwischen Cat und Dog auswählt. Dies konnte Probleme im Code verursachen.
Siehe auch
Referenz
Verwenden von Varianz für die generischen Delegaten Func und Action (C# und Visual Basic)