Compartir a través de


Crear interfaces genéricas variantes (C# y Visual Basic)

Puede declarar parámetros de tipo genérico en las interfaces como covariante o contravariante. La covarianza permite que los métodos de interfaz tengan tipos de valor devuelto más derivados que los definidos por los parámetros de tipo genérico. La contravarianza permite que los métodos de interfaz tengan tipos de argumento menos derivados que los especificados por los parámetros genéricos. Una interfaz genérica con parámetros de tipo genérico covariante o contravariante se denomina variante.

Nota

.NET Framework 4 incluye compatibilidad con la varianza para varias interfaces genéricas ya existentes. Para obtener una lista de las interfaces variantes de .NET Framework, vea Varianza en interfaces genéricas (C# y Visual Basic).

Declarar interfaces genéricas variantes

Para declarar interfaces genéricas variantes, use las palabras clave in y out para los parámetros de tipo genérico.

Nota importanteImportante

El parámetro ByRef de Visual Basic y los parámetros ref y out de C# no pueden ser variantes. Asimismo, los tipos de valor no admiten la varianza.

Puede declarar una covariante del parámetro de tipo genérico mediante la palabra clave out. El tipo covariante debe cumplir las siguientes condiciones:

  • El tipo se usa solamente como tipo de valor devuelto de los métodos de interfaz y no como tipo de los argumentos de método. Esto se muestra en el ejemplo siguiente, en el que el tipo R se declara como covariante.

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

    Hay una excepción para esta regla. Si tiene un delegado genérico contravariante como parámetro de método, puede usar el tipo como parámetro de tipo genérico para el delegado. Esto se muestra mediante el tipo R en el siguiente ejemplo. Para obtener más información, vea Varianza en delegados (C# y Visual Basic) y Usar la varianza para los delegados genéricos Func y Action (C# y 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);
    }
    
  • El tipo no se usa como restricción genérica para los métodos de interfaz. Esto se muestra en el ejemplo de código siguiente.

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

Puede declarar una contravariante del parámetro de tipo genérico mediante la palabra clave in. El tipo contravariante solo se puede usar como tipo de los argumentos de método, y no como tipo de valor devuelto de los métodos de interfaz. El tipo contravariante también se puede usar para las restricciones genéricas. En el código siguiente se muestra cómo declarar una interfaz contravariante y usar una restricción genérica para uno de los métodos.

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

También es posible admitir la covarianza y la contravarianza en la misma interfaz, pero para distintos parámetros de tipo, como se muestra en el ejemplo de código siguiente.

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

En Visual Basic, no se pueden declarar eventos en interfaces variantes sin especificar el tipo de delegado. Además, una interfaz variante no puede tener clases, enumeraciones o estructuras anidadas, pero sí interfaces anidadas. Esto se muestra en el ejemplo de código siguiente.

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

Implementar interfaces genéricas variantes

Para implementar interfaces genéricas variantes en las clases se usa la misma sintaxis utilizada para las interfaces invariables. En el ejemplo de código siguiente se muestra cómo implementar una interfaz covariante en una clase genérica.

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

Las clases que implementan interfaces variantes son invariables. Por ejemplo, considere el fragmento de código siguiente:

' 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;

Extender interfaces genéricas variantes

Al extender una interfaz genérica variante, tiene que usar las palabras clave in y out para especificar de forma explícita si la interfaz derivada admite la varianza. El compilador no infiere la varianza a partir de la interfaz que se extiende. Por ejemplo, considere las siguientes interfaces.

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> { }

En la interfaz IInvariant<T> (Invariant(Of T) en Visual Basic), el parámetro de tipo genérico T es invariable, mientras que en IExtCovariant<out T> (IExtCovariant (Of Out T) en Visual Basic) el parámetro de tipo es covariante, si bien ambas interfaces extienden la misma interfaz. La misma regla se aplica a los parámetros de tipo genérico contravariante.

Puede crear una interfaz que extienda tanto la interfaz donde el parámetro de tipo genérico T es covariante como la interfaz donde es contravariante si en la interfaz extensible el parámetro de tipo genérico T es invariable. Esto se muestra en el siguiente ejemplo de código:

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> { }

Sin embargo, si un parámetro de tipo genérico T se declara como covariante en una interfaz, no se puede declarar como contravariante en la interfaz extensible, o viceversa. Esto se muestra en el siguiente ejemplo de código:

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> { }

Evitar la ambigüedad

Al implementar interfaces genéricas variantes, la varianza puede provocar ambigüedad en algunas ocasiones. Esto debería evitarse.

Por ejemplo, si se implementa explícitamente la misma interfaz genérica variante con distintos parámetros de tipo genérico en una clase, puede crearse ambigüedad. El compilador no genera un error en este caso, pero no se especifica qué implementación de interfaz se elegirá en el momento de la ejecución. Esto podría provocar errores imperceptibles en el código. Considere el ejemplo de código siguiente.

Nota

Con Option Strict Off, Visual Basic genera una advertencia del compilador cuando se produce una implementación de interfaz ambigua. Con Option Strict On, Visual Basic genera un error del compilador.

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

En este ejemplo, no se especifica cómo elige el método pets.GetEnumerator entre Cat y Dog. Esto podría causar problemas en el código.

Vea también

Referencia

Usar la varianza para los delegados genéricos Func y Action (C# y Visual Basic)

Conceptos

Varianza en interfaces genéricas (C# y Visual Basic)