¿Por qué IEnumerable se hizo covariante en C # 4?

En versiones anteriores de C # IEnumerable se definió así:

 public interface IEnumerable : IEnumerable 

Como C # 4 la definición es:

 public interface IEnumerable : IEnumerable 
  • ¿Es solo para hacer que los moldes molestos en expresiones LINQ desaparezcan?
  • ¿No introducirá esto los mismos problemas que con la string[] <: object[] (varianza de matriz rota) en C #?
  • ¿Cómo se realizó la adición de la covarianza desde un punto de vista de compatibilidad? ¿Funcionará el código anterior en las versiones posteriores de .NET o aquí es necesario volver a comstackr? ¿Qué acerca del otro camino alrededor?
  • ¿El código anterior que utilizaba esta interfaz era estrictamente invariante en todos los casos o es posible que ciertos casos de uso se comporten de manera diferente ahora?

Las respuestas de Marc y CodeInChaos son bastante buenas, pero solo para agregar algunos detalles más:

En primer lugar, parece que estás interesado en conocer el proceso de diseño que realizamos para crear esta función. Si es así, te animo a leer mi larga serie de artículos que escribí mientras diseñaba e implementaba la función. Comienza desde abajo:

http://blogs.msdn.com/b/ericlippert/archive/tags/covariance+and+contravariance/default.aspx

¿Es solo para hacer que los moldes molestos en expresiones LINQ desaparezcan?

No, no es solo para evitar Cast expresiones Cast , pero hacerlo fue uno de los motivadores que nos animó a hacer esta función. Nos dimos cuenta de que habría un aumento en el número de “¿por qué no puedo usar una secuencia de jirafas en este método que toma una secuencia de animales?” preguntas, porque LINQ alienta el uso de tipos de secuencias. Sabíamos que queríamos agregar covarianza a IEnumerable primero.

De hecho, consideramos hacer IEnumerable covariante incluso en C # 3, pero decidimos que sería extraño hacerlo sin introducir la función completa para que cualquiera la use.

¿No introducirá esto los mismos problemas que con la string[] <: object[] (varianza de matriz rota) en C #?

No introduce directamente ese problema porque el comstackdor solo permite la variación cuando se sabe que es seguro. Sin embargo, conserva el problema de variación de matriz rota. Con covarianza, IEnumerable es implícitamente convertible a IEnumerable , por lo que si tiene una secuencia de matrices de cadenas, puede tratar eso como una secuencia de matrices de objetos, y entonces tiene el mismo problema que antes : puedes intentar colocar una jirafa en ese conjunto de cadenas y obtener una excepción en el tiempo de ejecución.

¿Cómo se realizó la adición de la covarianza desde un punto de vista de compatibilidad?

Cuidadosamente.

¿Funcionará el código anterior en las versiones posteriores de .NET o aquí es necesario volver a comstackr?

Sólo hay una forma de averiguarlo. Pruébalo y ve lo que falla!

A menudo es una mala idea tratar de obligar al código comstackdo contra .NET X a ejecutarse contra .NET Y si X! = Y, independientemente de los cambios en el sistema de tipos.

¿Qué acerca del otro camino alrededor?

La misma respuesta.

¿Es posible que ciertos casos de uso se comporten de manera diferente ahora?

Absolutamente. Hacer una interfaz covariante donde era invariante antes es técnicamente un "cambio de ruptura" porque puede provocar la ruptura del código de trabajo. Por ejemplo:

 if (x is IEnumerable) ABC(); else if (x is IEnumerable) DEF(); 

Cuando IE no es covariante, este código elige ABC o DEF o ninguno. Cuando es covariante, nunca más elige DEF.

O:

 class B { public void M(IEnumerable turtles){} } class D : B { public void M(IEnumerable animals){} } 

Antes, si llamaba a M en una instancia de D con una secuencia de tortugas como argumento, la resolución de sobrecarga elige BM porque ese es el único método aplicable. Si IE es covariante, la resolución de sobrecarga ahora elige DM porque ambos métodos son aplicables, y un método aplicable en una clase más derivada siempre supera un método aplicable en una clase menos derivada, independientemente de si la coincidencia de tipo de argumento es exacta o no. .

O:

 class Weird : IEnumerable, IEnumerable { ... } class B { public void M(IEnumerable bananas) {} } class D : B { public void M(IEnumerable animals) {} public void M(IEnumerable fruits) {} } 

Si IE es invariable, una llamada a dM(weird) resuelve en BM Si IE se convierte de repente en covariante, ambos métodos son aplicables, ambos son mejores que el método en la clase base, y ninguno es mejor que el otro, entonces, resolución de sobrecarga se vuelve ambiguo y reportamos un error.

Cuando decidimos hacer estos cambios definitivos, esperábamos que (1) las situaciones fueran raras, y (2) cuando surgen situaciones como esta, casi siempre es porque el autor de la clase está intentando simular la covarianza en un idioma eso no lo tiene Al agregar covarianza directamente, con suerte, cuando el código "se rompe" en la recomstackción, el autor simplemente puede eliminar el engranaje loco intentando simular una función que ahora existe.

En orden:

¿Es solo para hacer que los moldes molestos en expresiones LINQ desaparezcan?

Hace que las cosas se comporten como la gente generalmente espera ; p

¿No introducirá esto los mismos problemas que con la string[] <: object[] (varianza de matriz rota) en C #?

No; ya que no expone ningún mecanismo Add o similar (y no puede; out y in se aplican en el comstackdor)

¿Cómo se realizó la adición de la covarianza desde un punto de vista de compatibilidad?

La CLI ya lo soportaba, esto simplemente hace que C # (y algunos de los métodos BCL existentes) lo noten

¿Funcionará el código anterior en las versiones posteriores de .NET o aquí es necesario volver a comstackr?

Sin embargo, es completamente retrocompatible: C # que se basa en la variación de C # 4.0 no se comstackrá en un comstackdor de C # 2.0 etc.

¿Qué acerca del otro camino alrededor?

Eso no es irracional

¿El código anterior que utilizaba esta interfaz era estrictamente invariante en todos los casos o es posible que ciertos casos de uso se comporten de manera diferente ahora?

Algunas llamadas BCL ( IsAssignableFrom ) pueden volver de forma diferente ahora

¿Es solo para hacer que los moldes molestos en expresiones LINQ desaparezcan?

No solo cuando usas LINQ. Es útil en cualquier lugar donde tenga un IEnumerable y el código espera un IEnumerable .

¿No introducirá esto los mismos problemas que con la string[] <: object[] (varianza de matriz rota) en C #?

No, porque la covarianza solo se permite en interfaces que devuelven valores de ese tipo, pero no los acepta. Entonces es seguro.

¿Cómo se realizó la adición de la covarianza desde un punto de vista de compatibilidad? ¿Funcionará el código anterior en las versiones posteriores de .NET o aquí es necesario volver a comstackr? ¿Qué acerca del otro camino alrededor?

Creo que el código ya comstackdo funcionará como está. Algunas comprobaciones de tipo de tiempo de ejecución ( is , IsAssignableFrom , …) devolverán true donde hayan devuelto false anteriormente.

¿El código anterior que utilizaba esta interfaz era estrictamente invariante en todos los casos o es posible que ciertos casos de uso se comporten de manera diferente ahora?

No estoy seguro de lo que quieres decir con eso


Los mayores problemas están relacionados con la resolución de sobrecarga. Como ahora son posibles más conversiones implícitas, se puede elegir una sobrecarga diferente.

 void DoSomething(IEnumerabe bla); void DoSomething(object blub); IEnumerable values = ...; DoSomething(values); 

Pero, por supuesto, si estas sobrecargas se comportan de manera diferente, la API ya está mal diseñada.