¿Cómo se implementan la covarianza y contravarianza genéricas en C # 4.0?

106

No asistí a PDC 2008, pero escuché algunas noticias de que C # 4.0 se anunció para admitir la covarianza y contravarianza genéricas. Es decir, List<string>se puede asignar a List<object>. ¿Cómo es posible?

En el libro C # in Depth de Jon Skeet , se explica por qué los genéricos de C # no admiten la covarianza y la contravarianza. Es principalmente para escribir código seguro. Ahora, C # 4.0 cambió para admitirlos. ¿Traería el caos?

¿Alguien sabe los detalles sobre C # 4.0 puede dar alguna explicación?

Morgan Cheng
fuente
Aquí hay un buen artículo que cubre las próximas implementaciones de covarianza y contravarianza en delegados e interfaces en C # 4.0: LINQ Farm: Covarianza y contravarianza en C # 4.0
CMS
Anders Noråse explica en C # 4.0 - Covarianza y contravarianza el concepto y muestra que ya se admite hoy en IL desde .NET 2.0.
Thomas Freudenberg

Respuestas:

155

La varianza solo se admitirá de manera segura ; de hecho, utilizando las habilidades que ya tiene el CLR. Entonces, los ejemplos que doy en el libro de tratar de usar a List<Banana>como List<Fruit>(o lo que sea) todavía no funcionarán, pero algunos otros escenarios sí lo harán.

En primer lugar, solo será compatible con interfaces y delegados.

En segundo lugar, requiere que el autor de la interfaz / delegado decore los parámetros de tipo como in(para contravarianza) o out(para covarianza). El ejemplo más obvio es el IEnumerable<T>que sólo le permite quitarle valores, no le permite agregar valores nuevos. Eso se convertirá IEnumerable<out T>. Eso no perjudica la seguridad de tipos en absoluto, pero le permite devolver un IEnumerable<string>de un método declarado para devolver, IEnumerable<object>por ejemplo.

La contravarianza es más difícil de dar ejemplos concretos para el uso de interfaces, pero es fácil con un delegado. Considere Action<T>: eso solo representa un método que toma un Tparámetro. Sería bueno poder convertir sin problemas el uso de an Action<object>como Action<string>: cualquier método que tome un objectparámetro estará bien cuando se presente con un string. Por supuesto, C # 2 ya tiene covarianza y contravarianza de delegados hasta cierto punto, pero a través de una conversión real de un tipo de delegado a otro (creando una nueva instancia); consulte P141-144 para ver ejemplos. C # 4 hará que esto sea más genérico y (creo) evitará crear una nueva instancia para la conversión. (En su lugar, será una conversión de referencia).

Espero que esto lo aclare un poco; ¡avíseme si no tiene sentido!

Jon Skeet
fuente
3
Entonces, ¿significa que si la clase se declara como "List <out T>", entonces NO debería tener una función miembro como "void Add (T obj)"? El compilador de C # 4.0 informará un error al respecto, ¿verdad?
Morgan Cheng
1
Morgan: Eso es ciertamente lo que entiendo, sí.
Jon Skeet
4
una vez más, una de sus respuestas aquí en SO inmediatamente me ayudó a mejorar algo de código. ¡Gracias!
Marcos
@ Ark-kun: Sí, soy consciente de eso. De ahí el "todavía no funcionará" en la misma oración. (Y también soy consciente de las razones)
Jon Skeet
@JonSkeet ¿Es correcto que "solo puedes usar a List<Banana>como IList<Fruit>" como dijo @ Ark-kun? Si es así, cómo es posible, aunque el parámetro de tipo de la IList<T>interfaz no está definido como covariante (no out T, sino simplemente T).
gehho