2012-06-18 11 views
9

He estado leyendo sobre los cambios que traerá .NET4.5, y en el blog this tropecé con algo que ni sabía ni entendía.¿Cómo el lenguaje C# evita que los genéricos sean covariables a menos que no contengan ningún método que requiera T como entrada?

Cuando se habla de la puesta en práctica de las colecciones de sólo lectura, Immo Landwerth dice:

Desafortunadamente, nuestro sistema de tipo no permite hacer tipos de T covariante a menos que no tiene métodos que toman T como una entrada. Por lo tanto, no podemos agregar un método IndexOf a IReadOnlyList. Creemos que esto es un pequeño sacrificio en comparación con no tener soporte para la covarianza.

Desde mi limitada comprensión, obviamente, parece que él está diciendo que para que nos permita llamar a un método que requiere una IReadOnlyList<Shape> pasando un IReadOnlyList<Circle>, no podemos tener un método IReadOnlyList<T>.IndexOf(T someShape).

No veo cómo el sistema de tipo podría evitar eso. ¿Alguien puede explicar?

Respuesta

11

Suponer Circle implementa IEquatable<Circle>. Eso sería usado naturalmente por IReadOnlyList<Circle>.IndexOf si estuviera disponible. Ahora bien, si usted podría escribir esto:

IReadOnlyList<Circle> circles = ...; 
IReadOnlyList<Shape> shapes = circles; 
int index = shapes.IndexOf(new Square(10)); 

que terminaría tratando de pasar un Square a Circle.Equals(Circle) que sería claramente una mala idea.

Las reglas que aplican los "valores cero de T en las posiciones de entrada" se encuentran en la sección 13.1.3 de la especificación C# 4. También debe leer Eric Lippert's blog series on generic variance para un lote más detalles.

+0

Bastante. Pensé que IndexOf simplemente devolvería -1 si recibió un tipo incorrecto. No sé por qué pensé eso, eso implicaría necesitar algún tipo de comprobación de tipo en cada método. Entraré en las publicaciones de blog de eric. ¡Pero ese hombre puede explicar algo de una manera que yo pueda entender! –

+0

Lo que una covariante 'IReadOnlyList ' podría implementar sería 'IndexOf (TT it)', donde el tipo de parámetro del método era independiente del de la colección. Una clase 'List ' podría implementar su 'IndexOf' podría entonces usar un delegado generado de forma perezosa (en caché) para cada tipo' TT', de modo que si 'TItem' implementa' IEquatable .Equals' crearía un delegado a un método estático que llamaría eso, y de lo contrario crearía un delegado a un método estático que usó alguna otra comparación o, para algunas combinaciones de tipos 'TT' y' T', simplemente devolvió -1. – supercat

1

Tenga en cuenta que sería correcto, en teoría (no estoy seguro si es que se implementa en .NET aún), para definir IndexOf para todos los subtipos de T:

class ReadOnlyList<+T> = { ... 
    Int IndexOf<U>(U elem) where T : U { ... } 
} 

Es interesante ver qué haría IndexOf(T elem) obviamente no será covariante, mientras que este es: si tiene T2 : T1, un método IndexOf(T1 elem) puede no ser compatible con la firma IndexOf(T2 elem). Por el contrario, si tiene IndexOf<U>(U elem) where T1 : U, sabe que T2 : T1 y T1 : U, por lo que también tiene T2 : U para todos los tales U: este tipo es una subtipificación de IndexOf<U>(U elem) with T2 : U.

Esta observación se hace, por ejemplo, en el artículo de Variance and Generalized Constraints for C# Generics de Emir, Kennedy, Russo y Yu: presentan un sistema de tipos que aceptaría esta definición.

+0

No creo que el parámetro del genérico 'IndexOf' deba restringirse. Es perfectamente razonable preguntar a 'List ' si contiene una instancia determinada de 'Animal', o' SiameseCat', o incluso 'Dog'. – supercat

+0

Todos esos ejemplos estarían permitidos con la firma que di, usando la subtipificación en el argumento o en la propia lista. 'SiameseCat' es un supertipo de' Cat' por lo que puede usar directamente 'List .indexOf '. Una 'Lista ' también es una 'Lista ' por lo que puede usar 'List .indexOf ' o 'List .indexOf '. La comparación se realizará en la superclase común más precisa de los dos objetos. – gasche

+0

Mi punto es que no hay nada de malo con 'ReadableList .IndexOf '. Si el método 'foo' en una clase con parámetros de tipo genérico no relacionados' T' y 'U' recibe una' ListaRedableList ' y un 'U item', y quiere saber si' list' contiene 'item', esa consulta ser significativo si 'T' es una subclase o superclase de' U', y tendría una semántica bien definida incluso cuando los tipos no están relacionados. La consulta no debería requerir 'foo' para determinar ninguna relación de clase entre' T' y 'U', especialmente dado que los tipos reales de la lista y el elemento pueden permitir que el método IndexOf ... – supercat

6

Dado que IReadOnlyList<T> es covariante, puede convertirlo en cualquier supertipo de T y todos los métodos deben funcionar de acuerdo con el contrato. El supertipo más super de T es Object, por lo que si IndexOf formaban parte de la interfaz, debería haber aceptado Object.

Como dice Jon Skeet, en una lista de objetos Circle, puede preguntar: ¿cuál es el índice de un particular Square en esta lista? La única respuesta correcta sería "no está aquí", y IndexOf debería devolver -1 y no lanzar una excepción.

Entonces, no estoy de acuerdo con Jon Skeet. Teniendo en cuenta las limitaciones de los parámetros genéricos covariantes, y similar a ArrayList.indexOf in Java, el equipo BCL debería haber incluido un método IndexOf con la siguiente firma:

int IndexOf(object item); 

Exactamente el mismo argumento se aplica a Contains en IReadOnlyCollection: cuando se pasa de un objeto de un tipo incompatible, la colección obviamente no lo contiene y el método debería simplemente devolver false.

El único inconveniente sería el boxeo de tipos de valores, pero la implementación real aún puede ocultar el método IReadOnlyList.IndexOf y proporcionar su propia sobrecarga genérica, lo que hace que este argumento sea discutible.

Por lo tanto, tiene razón al esperar que IndexOf devuelva -1 cuando pasó un objeto incompatible, si estuviera en la interfaz.


he implementado este principio en mi biblioteca M42.Collections para mostrar cómo funcionaría en la práctica. Puede descargarlo aquí:

M42 Collections - Biblioteca portátil .NET para trabajar con colecciones correctamente.

+0

El parámetro para 'Igual' es gracioso, ya que desde el punto de vista del rendimiento podría de ser genérico, pero desde un punto de vista semántico es tanto covariante como contravariante (lo que significa que no es realmente genérico). Es perfectamente razonable considerar 'List ' como una "cosa que se puede preguntar si contiene un' SiameseCat' dado ", o" cosa ... dado 'Animal'", o incluso "cosa ... dado' Perro' ". La respuesta en este último caso sería "no", pero eso no significa que la pregunta no pueda formularse. – supercat

Cuestiones relacionadas