Varianza en interfaces genéricas (C#)
En .NET Framework 4 se ha presentado la compatibilidad con la varianza para varias interfaces genéricas existentes. La compatibilidad con la varianza permite la conversión implícita de clases que implementan estas interfaces.
A partir de .NET Framework 4, las siguientes interfaces son variantes:
IEnumerable<T> (T es covariante)
IEnumerator<T> (T es covariante)
IQueryable<T> (T es covariante)
IGrouping<TKey,TElement> (
TKey
yTElement
son covariantes)IComparer<T> (T es contravariante)
IEqualityComparer<T> (T es contravariante)
IComparable<T> (T es contravariante)
A partir de .NET Framework 4.5, las siguientes interfaces son variantes:
IReadOnlyList<T> (T es covariante)
IReadOnlyCollection<T> (T es covariante)
La covarianza permite que un método tenga un tipo de valor devuelto más derivado que los que se definen en los parámetros de tipo genérico de la interfaz. Para ilustrar la característica de la covarianza, considere estas interfaces genéricas: IEnumerable<Object>
y IEnumerable<String>
. La interfaz IEnumerable<String>
no hereda la interfaz IEnumerable<Object>
. En cambio, el tipo String
hereda el tipo Object
, y en algunos casos puede que quiera asignar objetos de estas interfaces entre sí. Esto se muestra en el ejemplo de código siguiente.
IEnumerable<String> strings = new List<String>();
IEnumerable<Object> objects = strings;
En versiones anteriores de .NET Framework, este código provoca un error de compilación en C# y, si Option Strict
está activado, en Visual Basic. Pero ahora puede usar strings
en lugar de objects
, como se muestra en el ejemplo anterior, porque la interfaz IEnumerable<T> es covariante.
La contravarianza permite que un método tenga tipos de argumento menos derivados que los que se especifican en el parámetro genérico de la interfaz. Para ilustrar la contravarianza, se presupone que ha creado una clase BaseComparer
para comparar instancias de la clase BaseClass
. La clase BaseComparer
implementa la interfaz IEqualityComparer<BaseClass>
. Como la interfaz IEqualityComparer<T> ahora es contravariante, puede usar BaseComparer
para comparar instancias de clases que heredan la clase BaseClass
. Esto se muestra en el ejemplo de código siguiente.
// Simple hierarchy of classes.
class BaseClass { }
class DerivedClass : BaseClass { }
// Comparer class.
class BaseComparer : IEqualityComparer<BaseClass>
{
public int GetHashCode(BaseClass baseInstance)
{
return baseInstance.GetHashCode();
}
public bool Equals(BaseClass x, BaseClass y)
{
return x == y;
}
}
class Program
{
static void Test()
{
IEqualityComparer<BaseClass> baseComparer = new BaseComparer();
// Implicit conversion of IEqualityComparer<BaseClass> to
// IEqualityComparer<DerivedClass>.
IEqualityComparer<DerivedClass> childComparer = baseComparer;
}
}
Para obtener más ejemplos, vea Usar la varianza en interfaces para las colecciones genéricas (C#).
La varianza para interfaces genéricas solo es compatible con tipos de referencia. Los tipos de valor no admiten la varianza. Por ejemplo, IEnumerable<int>
no puede convertirse implícitamente en IEnumerable<object>
, porque los enteros se representan mediante un tipo de valor.
IEnumerable<int> integers = new List<int>();
// The following statement generates a compiler error,
// because int is a value type.
// IEnumerable<Object> objects = integers;
También es importante recordar que las clases que implementan las interfaces variantes siguen siendo invariables. Por ejemplo, aunque List<T> implementa la interfaz covariante IEnumerable<T>, no puede convertir List<String>
en List<Object>
implícitamente. Esto se muestra en el siguiente código de ejemplo.
// The following line generates a compiler error
// because classes are invariant.
// List<Object> list = new List<String>();
// You can use the interface object instead.
IEnumerable<Object> listObjects = new List<String>();