c# - una - ¿Por qué las clases que implementan interfaces variantes permanecen invariables?
que es una interfaz en programacion (2)
En primer lugar, las clases siempre son invariables en C #. No puedes declarar una clase como esta:
// Invalid
public class Foo<out T>
En segundo lugar, y más importante para el ejemplo que ha dado, List<T>
no se pudo declarar como covariante o contravariante en T
todos modos, ya que tiene miembros que aceptan y devuelven valores de tipo T
Imagina si fuera covariante. Entonces podrías escribir esto (para la jerarquía obvia de la clase Fruit
):
List<Banana> bunchOfBananas = new List<Banana>();
// This would be valid if List<T> were covariant in T
List<Fruit> fruitBowl = bunchOfBananas;
fruitBowl.Add(new Apple());
Banana banana = bunchOfBananas[0];
¿Qué esperarías que hiciera esa última línea? Fundamentalmente, no debería poder agregar una referencia de Apple
a un objeto cuyo tipo de tiempo de ejecución real sea List<Banana>
. Si agrega una manzana a un manojo de plátanos, se cae. Créame, lo he intentado.
La última línea debería ser segura en términos de tipos: los únicos valores dentro de una List<Banana>
deberían ser null
o las referencias a instancias de Banana
o una subclase.
Ahora, en cuanto a por qué las clases no pueden ser covariantes incluso cuando podrían ser lógicamente ... Creo que eso introduce problemas a nivel de implementación, y también sería muy restrictivo a nivel de programación. Por ejemplo, considera esto:
public class Foo<out T> // Imagine if this were valid
{
private T value;
public T Value { get { return value; } }
public Foo(T value)
{
this.value = value;
}
}
Probablemente aún tenga que ser inválido: la variable aún se puede escribir, lo que significa que cuenta como un espacio "en". Tendría que hacer todas las variables de tipo T
de solo lectura ... y eso es solo para empezar. Sospecho fuertemente que habría problemas más profundos.
En términos de pragmatismo puro, el CLR ha soportado delegado y la varianza de interfaz de v2 - C # 4 acaba de introducir la sintaxis para exponer la característica. No creo que el CLR alguna vez haya respaldado la varianza genérica de clase.
C # 4.0 ha ampliado aún más la co y la contravariación para los tipos e interfaces genéricos. Algunas interfaces (como IEnumerable<T>
) son covariantes, por lo que puedo hacer cosas como:
IEnumerable<object> ie = new List<string>();
pero ¿qué pasa con esta línea? Tengo un error en tiempo de compilación
List<Object> list = new List<String>();
//Cannot implicitly convert type List<string>'' to List<object>''
Quiero decir, si List<T>
implementa IEnumerable<T>
¿por qué List<T>
sigue siendo invariante? ¿Existe un buen contraejemplo que explique por qué esto no debe permitirse en C #?
Si queremos discutir agregar (aceptar) algo (T) a la Lista, debemos hablar sobre CONTRARANTIA (COVARIANZA no permite aceptar), entonces:
List<Fruit> bunchOfFruits = new List<Fruit>();
// This would be valid if List<T> were contravariant in T
List<Banana> bunchOfBananas = bunchOfFruits;
bunchOfBananas.Add(new Apple()); // not possible! We have compile error, coz in spite of Apple is a Fruit it is not ancestor of Banana. No logical mistakes.
bunchOfBananas.Add(new BigBanana()); // possible coz of rules of C#! we deal with a descendant of Banana
bunchOfBananas.Add(new Fruit()); // possible, coz CONTRAVARIANCE of List. We deal with the ancestor of Banana. No logical mistakes.
Entonces, como podemos ver, VARIANCE debería funcionar tanto para las clases como para las interfaces y los delegados (clases en general, no solo para colecciones). Y creo que se puede implementar en versiones futuras de .NET.