Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Puede declarar parámetros de tipo genérico en interfaces como covariante o contravariante. La Covarianza permite que los métodos de interfaz tengan más tipos devueltos derivados que los definidos por los parámetros de tipo genérico. Contravariance permite que los métodos de interfaz tengan tipos de argumentos menos derivados que los especificados por los parámetros genéricos. Una interfaz genérica que tiene parámetros de tipo genérico covariante o contravariante se denomina variante.
Nota:
.NET Framework 4 introdujo compatibilidad de varianza para varias interfaces genéricas existentes. Para obtener la lista de las interfaces variant en .NET, vea Varianza en interfaces genéricas (C#).
Declarar interfaces genéricas variantes
Puede declarar interfaces genéricas variantes mediante las in
palabras clave y out
para los parámetros de tipo genérico.
Importante
ref
Los parámetros , in
y out
en C# no pueden ser variantes. Los tipos de valor tampoco admiten la varianza.
Puede declarar un parámetro de tipo genérico covariante mediante la out
palabra clave . El tipo covariante debe cumplir las condiciones siguientes:
El tipo solo se utiliza como tipo de retorno en los métodos de una interfaz y no se utiliza como tipo de argumentos de método. Esto se ilustra en el ejemplo siguiente, en el que el tipo
R
se declara covariante.interface ICovariant<out R> { R GetSomething(); // The following statement generates a compiler error. // void SetSomething(R sampleArg); }
Hay una excepción a 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 ilustra mediante el tipo
R
en el ejemplo siguiente. Para obtener más información, vea Varianza en delegados (C#) y Usar la varianza para los delegados genéricos Func y Action (C#).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 código siguiente.
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; }
Puede declarar un contravariante de parámetro de tipo genérico mediante la in
palabra clave . El tipo contravariante solo se puede utilizar como tipo de argumentos en métodos y no como tipo de retorno en métodos de interfaz. El tipo contravariante también se puede usar para 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 sus métodos.
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 covarianza y contravarianza en la misma interfaz, pero para parámetros de tipo diferentes, como se muestra en el ejemplo de código siguiente.
interface IVariant<out R, in A>
{
R GetSomething();
void SetSomething(A sampleArg);
R GetSetSomethings(A sampleArg);
}
Implementar interfaces genéricas variantes
Las interfaces genéricas variantes se implementan en clases mediante la misma sintaxis que se usa 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<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 código siguiente.
// 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, debe usar las in
palabras clave y out
para especificar explícitamente si la interfaz derivada admite varianza. El compilador no deduce la varianza de la interfaz que se está ampliando. Por ejemplo, considere las siguientes interfaces.
interface ICovariant<out T> { }
interface IInvariant<T> : ICovariant<T> { }
interface IExtCovariant<out T> : ICovariant<T> { }
En la IInvariant<T>
interfaz, el parámetro T
de tipo genérico es invariable, mientras que en IExtCovariant<out T>
el parámetro de tipo es covariante, aunque 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 la interfaz donde el parámetro T
de tipo genérico es covariante y la interfaz donde es contravariante si en la interfaz de extensión el parámetro T
de tipo genérico es invariable. Esto se muestra en el ejemplo de código siguiente.
interface ICovariant<out T> { }
interface IContravariant<in T> { }
interface IInvariant<T> : ICovariant<T>, IContravariant<T> { }
Sin embargo, si un parámetro T
de tipo genérico se declara covariante en una interfaz, no se puede declarar contravariante en la interfaz de extensión, o viceversa. Esto se muestra en el ejemplo de código siguiente.
interface ICovariant<out T> { }
// The following statement generates a compiler error.
// interface ICoContraVariant<in T> : ICovariant<T> { }
Evitar ambigüedad
Al implementar interfaces genéricas variantes, la varianza a veces puede provocar ambigüedad. Esta ambigüedad debe evitarse.
Por ejemplo, si implementa explícitamente la misma interfaz genérica variante con distintos parámetros de tipo genérico en una clase, puede crear ambigüedad. El compilador no genera un error en este caso, pero no se especifica qué implementación de interfaz se elegirá en tiempo de ejecución. Esta ambigüedad podría provocar errores sutiles en el código. Observe el siguiente ejemplo de código.
// 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 pets.GetEnumerator
método entre Cat
y Dog
. Esto podría causar problemas en el código.