Covarianza de C # en tipos de retorno de subclase

¿Alguien sabe por qué los tipos de retorno covariantes no son compatibles con C #? Incluso cuando se intenta usar una interfaz, el comstackdor se queja de que no está permitida. Vea el siguiente ejemplo.

class Order { private Guid? _id; private String _productName; private double _price; protected Order(Guid? id, String productName, double price) { _id = id; _productName = productName; _price = price; } protected class Builder : IBuilder { public Guid? Id { get; set; } public String ProductName { get; set; } public double Price { get; set; } public virtual Order Build() { if(Id == null || ProductName == null || Price == null) throw new InvalidOperationException("Missing required data!"); return new Order(Id, ProductName, Price); } } } class PastryOrder : Order { PastryOrder(Guid? id, String productName, double price, PastryType pastryType) : base(id, productName, price) { } class PastryBuilder : Builder { public PastryType PastryType {get; set;} public override PastryOrder Build() { if(PastryType == null) throw new InvalidOperationException("Missing data!"); return new PastryOrder(Id, ProductName, Price, PastryType); } } } interface IBuilder { T Build(); } public enum PastryType { Cake, Donut, Cookie } 

Gracias por cualquier respuesta.

En primer lugar, la contravariancia del tipo de retorno no tiene ningún sentido; Creo que estás hablando de covarianza de tipo de retorno.

Vea esta pregunta para más detalles:

¿Apoya C # la covarianza de tipo de retorno?

Desea saber por qué la característica no está implementada. phoog es correcto; la característica no se implementa porque nadie aquí la implementó. Un requisito necesario pero insuficiente es que los beneficios de la característica excedan sus costos.

Los costos son considerables La función no es compatible de forma nativa por el tiempo de ejecución, funciona directamente en contra de nuestro objective para hacer C # versionable porque introduce otra forma del problema de clase base frágil, Anders no cree que sea una característica interesante o útil, y si realmente lo quiere, puede hacerlo funcionar escribiendo pequeños métodos auxiliares. (Que es exactamente lo que hace la versión CIL de C ++).

Los beneficios son pequeños

Las características de pequeño costo y beneficio pequeño con una solución fácil son aleatorizadas muy rápidamente . Tenemos prioridades mucho más altas.

El parámetro genérico contravariante no se puede generar, porque no se puede garantizar que sea seguro en tiempo de comstackción, y los diseñadores de C # tomaron la decisión de no prolongar las comprobaciones necesarias en el tiempo de ejecución.

Esta es la respuesta corta, y aquí hay una más breve …

¿Qué es la varianza?

La varianza es una propiedad de una transformación aplicada a una jerarquía de tipos:

  • Si el resultado de la transformación es una jerarquía de tipos que mantiene la “dirección” de la jerarquía de tipos original, la transformación es covariante.
  • Si el resultado de la transformación es una jerarquía de tipos que invierte la “dirección” original, la transformación es contravariante.
  • Si el resultado de la transformación es un grupo de tipos no relacionados, la transformación está en -variante.

¿Cuál es la variación en C #?

En C #, la “transformación” se está “usando como un parámetro genérico”. Por ejemplo, digamos que una clase Parent es heredada por la clase Child . Denotemos ese hecho como: Parent > Child (porque todas las instancias Child son también instancias Parent , pero no necesariamente al revés, por lo tanto, Parent es “más grande”). Digamos también que tenemos una interfaz genérica I :

  • Si I I , el T es covariante (se conserva la “dirección” original entre Parent y Child ).
  • Si I < I , el T es contravariante (la “dirección” original está invertida).
  • Si I no está relacionado con I , el T es invariante.

Entonces, ¿qué es potencialmente inseguro?

Si el comstackdor de C # realmente aceptó comstackr el siguiente código …

 class Parent { } class Child : Parent { } interface I { T Get(); // Imagine this actually compiles. } class G : I where T : new() { public T Get() { return new T(); } } // ... I g = new G(); // OK since T is declared as contravariant, thus "reversing" the type hierarchy, as explained above. Child child = g.Get(); // Yuck! 

… esto daría lugar a un problema en el tiempo de ejecución: un Parent se crea una instancia y se le asigna una referencia a Child . Como Parent no es un Child , ¡esto está mal!

La última línea se ve bien en tiempo de comstackción desde I.Get se declara para devolver Child , pero no pudimos “confiar” totalmente en el tiempo de ejecución. Los diseñadores de C # decidieron hacer lo correcto y detectar el problema completamente en tiempo de comstackción, y evitar cualquier necesidad de verificaciones en tiempo de ejecución (a diferencia de las matrices).

(Por razones similares pero “inversas”, el parámetro genérico covariante no se puede usar como entrada).

Eric Lippert ha escrito algunas publicaciones en este sitio acerca de la covarianza del método de devolución en las anulaciones de métodos, sin que yo pueda ver por qué la función no es compatible. Él mencionó, sin embargo, que no hay planes para apoyarlo: https://stackoverflow.com/a/4349584/385844

A Eric también le gusta decir que la respuesta a “por qué X no es compatible” es siempre la misma: porque nadie ha diseñado, implementado y probado (etc.) X. Un ejemplo de eso está aquí: https://stackoverflow.com/a/1995706/385844

Puede haber alguna razón filosófica para la falta de esta característica; quizás Eric verá esta pregunta y nos iluminará.

EDITAR

Como señaló Pratik en un comentario:

 interface IBuilder { T Build(); } 

debiera ser

 interface IBuilder { T Build(); } 

Eso le permitiría implementar PastryOrder : IBuilder , y entonces podría tener

 IBuilder builder = new PastryOrder(); 

Probablemente haya dos o tres enfoques que podría utilizar para resolver su problema, pero, como nota, la covarianza del método de retorno no es uno de esos enfoques, y ninguna de estas informaciones responde a la pregunta de por qué C # no lo admite.

Solo para publicar esto en algún lugar, google lo encuentra … Estaba investigando esto porque quería tener una interfaz en la que pueda devolver colecciones / enumerables de clases arbitrarias que implementan una interfaz específica.

Si está de acuerdo con la definición de los tipos concretos que desea devolver, simplemente puede definir su interfaz en consecuencia. Luego comprobará en tiempo de comstackción que se cumplen las restricciones (subtipo de lo que sea).

Proporcioné un ejemplo, que podría ayudarte.

Como señaló Branko Dimitrijevic, generalmente no es seguro permitir tipos de retorno covariantes en general. Pero al usar esto, es seguro e incluso puede anidar esto (por ejemplo, la interface A where T: B where U : C )

(Descargo de responsabilidad: empecé a usar C # ayer, por lo que podría estar completamente equivocado con respecto a las mejores prácticas, alguien con más experiencia debería comentar esto :))


Ejemplo:

Utilizando

 interface IProvider where T : ProvidedData where Coll : IEnumerable { Coll GetData(); } class XProvider : IProvider> { List GetData() { ... } } 

vocación

 new XProvider().GetData 

funciona y en este caso es seguro. Solo tiene que definir los tipos que desea devolver en este caso.


Más sobre esto: http://msdn.microsoft.com/de-de/library/d5x73970.aspx