2010-08-30 13 views
16

Por ejemplo IEnumerable<T> interfaz:¿Por qué las interfaces genéricas no son co/contravariantes por defecto?

public interface IEnumerable<out T> : IEnumerable 
{ 
    IEnumerator<T> GetEnumerator(); 
} 

En esta interfaz del tipo genérico se utiliza sólo como un tipo de retorno de método de interfaz y no se utiliza como un tipo de argumentos del método por lo tanto puede ser covariante. Dando esto, ¿no puede el compilador inferir teóricamente la varianza de la interfaz? Si puede, ¿por qué C# requiere que establezcamos palabras clave de co/contravarianza explícitamente?

actualización: Como Jon Skeet mencionó esta pregunta se puede spited en sub-preguntas:

  1. Puede compilador inferir de tipo genérico co/contravarianza por la forma en que se utiliza dentro de tipo genérico actual y todos son tipos básicos?

    Por ejemplo ... ¿Cuántos parámetros genéricos de interfaz de .NET Framework 4.0 se pueden marcar co/contravariante automáticamente sin ambigüedad alguna? Alrededor del 70%, 80%, 90% o 100%?

  2. Si puede, debe aplicar co/contravarianza a los tipos genéricos de forma predeterminada? Al menos para aquellos tipos que es capaz de analizar e inferir co/contravariancia del uso del tipo.

+2

Hay algunas excelentes respuestas a continuación con respecto a por qué esto no es automático. Solo quería agregar que ReSharper * * sugerirá co/contra-varianza e incluso hará la refactorización por usted si acepta sus sugerencias. Sin embargo, no sé qué tan bien funciona en las situaciones ambiguas/difíciles (supongo que no intentará una sugerencia en esos casos). –

+1

Excelente pregunta. Anticipé su pregunta en 2007 cuando estábamos diseñando esta característica. Por eso escribí un artículo que me contestaba en ese momento: http://blogs.msdn.com/b/ericlippert/archive/2007/10/29/covariance-and-contravariance-inc-c-seven-why- do-we-need-a-syntax-at-all.aspx –

Respuesta

16

Bueno, hay dos preguntas aquí. En primer lugar, podría el compilador siempre lo hacen? En segundo lugar, debería (si es posible)?

Para la primera pregunta, voy a aplazar a Eric Lippert, quien hizo este comentario cuando traje exactamente este tema en la segunda edición de C# en profundidad:

No está claro para mí que razonablemente podría incluso si quisiéramos. Podemos encontrar fácilmente situaciones que requieren un análisis global costoso de todas las interfaces en un programa para resolver las variaciones, y podemos encontrar situaciones en las cuales <in T, out U> o <out T, in U> y no hay manera de decidir entre ellas. Con un mal rendimiento y casos ambiguos, es una característica poco probable.

(espero que Eric no me importa citando esta palabra por palabra; él ha sido previamente muy bien acerca de compartir estos puntos de vista, así que voy por la forma pasada :)

Por otra parte, sospecho todavía hay casos en los que puede inferir sin ambigüedad, por lo que el segundo punto es todavía relevante ...

no creo que debería ser automática, incluso cuando el compilador sin ambigüedad puede saber que es válida en tan sólo de una sola mano. Mientras que expandir una interfaz siempre es un cambio radical en cierta medida, generalmente no lo es si usted es el único que lo implementa. Sin embargo, si las personas confían en que su interfaz será una variante, puede que no sea capaz de para agregar métodos sin romper clientes ... incluso si solo son llamadores, no implementadores. Los métodos que agreguen pueden cambiar una interfaz previamente covariante para convertirse en invariante, momento en el que interrumpe a las personas que llaman que intentan usarlo de forma covariante.

Básicamente, creo que es correcto exigir que esto sea explícito; es una decisión de diseño que debe tomar conscientemente, en lugar de simplemente terminar accidentalmente con covarianza/contravarianza sin haber pensado en ello.

+1

Si la covarianza/contravarianza de tipos genéricos puede llevar a la "ruptura de clientes", ¿por qué los creadores de .NET reemplazaron sus principales interfaces genéricas por contrapartes co/contravariantes en .NET 4.0 ? –

+1

@Koistya: No es así: es al revés. Si se ha inferido que es covariante, pero luego agrega un método que lo convierte en * invariante *, entonces cualquier cliente que esté utilizando la covarianza se atornillará. Editaré para explicar esto. –

+3

@Koistya: Hacer tipos invariantes en tipos de variantes es, de hecho, un cambio radical; decidimos que el beneficio del cambio valía la pena del descanso. Ver mi artículo sobre eso: http://blogs.msdn.com/b/ericlippert/archive/2007/11/02/covariance-and-contravariance-inc-c-part-nine-breaking-changes.aspx - Sin embargo, Jon señala correctamente que el tipo de pausa del que está hablando es que * la edición de una interfaz puede cambiar sus anotaciones de varianza legales de forma inesperada *. Por lo tanto, nos gustaría hacer esas anotaciones explícitas, en lugar de deducirlas. –

5

This article explica que hay situaciones en las que el compilador no puede inferir por lo que le proporciona la sintaxis explícita:

interface IReadWriteBase<T> 
{ 
    IReadWrite<T> ReadWrite(); 
} 

interface IReadWrite<T> : IReadWriteBase<T> 
{ 

} 

¿Qué inferir aquí in o out, tanto el trabajo?

+0

El compilador podría aplicar la palabra clave 'out' aquí de forma predeterminada a la interfaz base basada en el uso y lo mismo para la interfaz derivada basada en el padre T –

+0

No, ambos pueden. Pruébalo :-) –

+0

Sí, puedes configurar ambos manualmente, pero desde el punto de vista del compilador, podría establecer que T sea covariante por defecto en tu ejemplo. ¿Si o no? –

Cuestiones relacionadas