2010-01-03 22 views
15

Estaba leyendo una entrevista con Joshua Bloch en Coders at Work, donde lamentó la introducción de genéricos en Java 5. No le gusta la implementación específica en gran parte porque el soporte de varianza (comodines de Java) la hace innecesariamente compleja.C# generics - sin límites inferiores por diseño?

Por lo que sé, C# 3 no tiene nada que ver con los comodines explícitos y acotados, p. Ej. no puede declarar un método PriceBatch que toma un grupo de Asset o una subclase de activo (void PriceBatch(Collection<? extends Asset> assets) en Java?).

¿Alguien sabe por qué los comodines y los límites no se han agregado a C#? ¿Se dejaron intencionalmente estas características para simplificar el lenguaje, o es algo que simplemente no han llegado a implementar todavía?

EDITAR: Holy smoke, ¡comentarios de Eric Lippert himself! Después de leer el suyo y comentarios interesantes de Pablo, me doy cuenta de que por lo menos se admiten límites superiores y que el ejemplo anterior se puede traducir a C# como:

void PriceBatch<T>(ICollection<T> assets) where T : Asset 

Límites inferior, por el contrario, están aparentemente no soportado como Eric dice en su segundo comentario, por ejemplo probablemente no haya forma de traducir directamente este código Java (algo inventado) a C#:

public class Asset {} 
public class Derivative extends Asset {} 
public class VanillaOption extends Derivative {} 

public static <T extends Asset> void copyAssets(Collection<T> src, Collection<? super T> dst) { 
    for(T asset : src) dst.add(asset); 
} 

Collection<VanillaOption> src = new ArrayList<VanillaOption>(); 
[...] 
Collection<Derivative> dst = new ArrayList<Derivative>(); 
[...] 
copyAssets(src, dst); 

¿Estoy en lo correcto? Si este es el caso, ¿hay alguna razón particular por la cual C# tiene límites superiores pero no menores?

+5

No entiendo la pregunta; C# ha limitado los genéricos y desde C# 2. ¿Es lo que está obteniendo en Java que soporta * call-site * covarianza y contravarianza en los tipos genéricos construidos? Porque C# no es compatible con eso; C# 4 admitirá * la varianza del sitio de declaración *. –

+6

¿O es lo que se obtiene con el hecho de que los límites de parámetros de tipo en los métodos genéricos son solo límites derivados y no deben derivarse de los límites? C# no es compatible con este último; Java y Scala creo. –

+0

Gracias, he actualizado la pregunta. – ehnmark

Respuesta

20

Una pregunta complicada.

Primero consideremos su pregunta fundamental, "¿por qué es esto ilegal en C#?"

class C<T> where T : Mammal {} // legal 
class D<T> where Giraffe : T {} // illegal 

es decir, un tipo de delimitación genérica puede decir 'T debe haber ningún tipo de referencia que se puede asignar a una variable de tipo mamífero', pero no" T debe ser cualquier tipo de referencia, una variable de los cuales podría asignarse una jirafa ". ¿Por qué la diferencia?

No lo sé. Eso fue mucho antes de mi tiempo en el equipo C#. La respuesta trivial es" porque el CLR no lo admite ", pero el equipo que los genéricos C# diseñados fueron los del mismo equipo que diseñaron los genéricos CLR, por lo que en realidad no es una gran explicación.

Mi suposición sería simplemente que, como siempre, para ser compatible, una característica debe diseñarse, implementarse, probarse, documentarse y enviarse a los clientes; nadie hizo alguna de estas cosas para esta función y, por lo tanto, no está en el idioma. No veo un gran beneficio convincente para la función propuesta; características complicadas sin beneficios convincentes tienden a reducirse por aquí.

Sin embargo, eso es una suposición. La próxima vez que chatee con los chicos que trabajaron en genéricos, viven en Inglaterra, por lo que no es como si estuvieran en el pasillo, desafortunadamente, les preguntaré.

En cuanto a su ejemplo específico, creo que Paul está en lo cierto. No necesita restricciones de límite inferior para que funcione en C#. Se podría decir:

void Copy<T, U>(Collection<T> src, Collection<U> dst) where T : U 
{ 
    foreach(T item in src) dst.Add(item); 
} 

Es decir, poner la restricción en T, no en U.

+1

Gracias por el seguimiento.El libro Effective Java explica un escenario en el que sería útil: digamos que define un tipo 'MyStack ' con un método 'PopAll' que toma una colección de destino como parámetro. Si tiene un 'MyStack ', debería poder copiar sus elementos a 'List ', pero para expresar que tendría que decir algo como public void 'PopAll (ICollection dst) donde T: X' y eso parece ser ilegal en verdad. – ehnmark

+7

Sí, eso sería útil. Curiosamente, en C# aunque no puedes hacer eso directamente, * puedes * hacer eso haciendo un método de extensión en Stack . Esto introduce un nuevo parámetro de tipo que puede ser restringido. –

+0

Otro ejemplo que podría ver sería la covarianza de 'IImmutableList ' en 'System.Collections.Immutable'; actualmente, 'IImmutableList Add (T item)' impide eso, pero un 'IImmutableList Add (TNew item) donde TNew: T' lo guardará. (Bueno, para comenzar, no sería implementar la interfaz mutable en el tipo inmutable, pero supongo que tenían motivos para ir con ese diseño.) Aquí, los métodos de extensión no te llevarían tan lejos. – fuglede

8

C# 4 presenta nuevas características que permiten la covarianza y la contravarianza en genéricos.

Hay otros de los mensajes que hablan de esto con más detalle: How is Generic Covariance & Contra-variance Implemented in C# 4.0?

La nueva característica no permiten esto de forma automática en todos los tipos, pero hay una nueva sintaxis que permite a los desarrolladores especificar si los argumentos genéricos son covariante o contravariante.

Las versiones de C# anteriores a C# 4 tenían una funcionalidad limitada similar a esta en lo que respecta a los delegados y ciertos tipos de matriz. Con respecto a los delegados, se permiten los delegados que toman los tipos de parámetros básicos. Con respecto a los tipos de matriz, creo que es válida a menos que haya un boxeo involucrado. Es decir, una matriz de Cliente puede ser el caso de una matriz de objetos. Sin embargo, una matriz de entradas no se puede convertir en una matriz de objetos.

+0

Arrays, también: String [] también es un Object []. – codekaizen

+0

Actualización de mi respuesta, gracias. – Eilon

+2

Las colecciones/matrices mutables no deben ser variantes. De lo contrario, se rompe la seguridad tipo. –

7

.NET ya tiene el equivalente de comodines, con más lógica nombrados limitaciones de tipo genérico, que puede hacer lo que usted describe sin problemas

namespace ConsoleApplication3 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
     List<a> a = new List<a>(); 
     List<b> b = new List<b>(); 
     List<c> c = new List<c>(); 
     test(a); 
     test(b); 
     test(c); 

     } 

     static void test<T>(List<T> a) where T : a 
     { 
      return; 
     } 
    } 
    class a 
    { 

    } 
    class b : a 
    { 

    } 
    class c : b 
    { 

    } 
} 

Ejemplo 2

namespace ConsoleApplication3 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      ICollection<VanillaOption> src = new List<VanillaOption>(); 
     ICollection<Derivative> dst = new List<Derivative>(); 
     copyAssets(src, dst); 
     } 

     public static void copyAssets<T,G>(ICollection<T> src, ICollection<G> dst) where T : G { 
      foreach(T asset in src) 
       dst.Add(asset); 
     } 
    } 
    public class Asset {} 
    public class Derivative : Asset {} 
    public class VanillaOption : Derivative {} 
} 

Este ejemplo representa una conversión de código de tu ejemplo en java.

¡No estoy realmente en posición de responder la pregunta en sí!

+0

Hola, Paul, una pregunta: la implicación de su ejemplo de código es que la restricción genérica expresada en "donde T: a" ... en virtud del hecho de que "manejará" no solo una instancia de "a" en sí, sino , también, cualquier objeto que se haya descendido, sin importar cuán "indirectamente", desde "a": ¿es equivalente a comodines en genéricos en Java? Solo busco aclarar para mi propio beneficio; no hay crítica de su respuesta implícita o intencional. El hecho de que la letra "a" tenga tres significados diferentes (clase, variable interna y nombre de parámetro) en su ejemplo me dificultaba asimilar, fyi. – BillW

+0

Sí, no está claro, disculpas. He escrito un ejemplo de tu código de límite inferior, funcionalmente es el mismo, ¡pero no usa límites inferiores! –

+0

Hola Paul y gracias por tu respuesta actualizada. Mi pregunta es realmente por qué C# 3 solo tiene un subconjunto de soporte de varianza de Java a este respecto y si es una decisión intencional hacer "correctamente" lo que Java hizo "mal", o tiene que ver con limitaciones de CLR o lo que sea. Es difícil encontrar un ejemplo significativo de cuándo los límites inferiores serían útiles, como lo ilustra mi ejemplo artificial, y tal vez eso sea parte de la respuesta :) – ehnmark