Pregunta

Tengo una clase base:

public abstract class StuffBase
{
    public abstract void DoSomething();
}

Y dos clases derivadas

public class Stuff1 : StuffBase
{
    public void DoSomething()
    {
        Console.WriteLine("Stuff 1 did something cool!");
    }
    public Stuff1()
    {
        Console.WriteLine("New stuff 1 reporting for duty!");
    }
}

public class Stuff2 : StuffBase
{
    public void DoSomething()
    {
        Console.WriteLine("Stuff 2 did something cool!");
    }
    public Stuff1()
    {
        Console.WriteLine("New stuff 2 reporting for duty!");
    }
}

Bien, ahora digamos que tengo una lista de elementos:

var items = new List<StuffBase>();
items.Add(new Stuff1());
items.Add(new Stuff2());

y quiero que todos llamen a su método DoSomething (). Podría esperar simplemente iterar la lista y llamar a su método DoSomething (), así que digamos que tengo un método para hacer eso llamado AllDoSomething () que simplemente itera sobre la lista y hace el trabajo:

public static void AllDoSomething(List<StuffBase> items)
{
    items.ForEach(i => i.DoSomething());
}

¿Cuál es la diferencia práctica del siguiente método?

public static void AllDoSomething<T>(List<T> items) where T: StuffBase
{
    items.ForEach(i => i.DoSomething());
}

Ambos métodos aparecen en términos reales, aunque son sintácticamente diferentes, para estar haciendo lo mismo.

¿Son solo diferentes formas de hacer lo mismo? Entiendo los genéricos y las restricciones de tipo, pero no puedo ver por qué usaría un camino sobre el otro en este caso.

¿Fue útil?

Solución

Esto se debe a que, hasta el momento, C # no admite Covarianza .

  

Más formalmente, en C # v2.0 si T es un   subtipo de U, entonces T [] es un subtipo de   U [], pero G no es un subtipo de G   (donde G es cualquier tipo genérico). En   terminología de la teoría de tipos, describimos   este comportamiento diciendo que C # array   los tipos son & # 8220; covariante & # 8221; y genérico   los tipos son & # 8220; invariante & # 8221 ;.

Referencia: http: // blogs. msdn.com/rmbyers/archive/2005/02/16/375079.aspx

Si tiene el siguiente método:

public static void AllDoSomething(List<StuffBase> items)
{
    items.ForEach(i => i.DoSomething());
}

var items = new List<Stuff2>();
x.AllDoSomething(items); //Does not compile

Donde, como si usa la restricción de tipo genérico, lo hará.

Para obtener más información sobre covarianza y contravarianza], consulte La serie de publicaciones de Eric Lippert .


Otras publicaciones que vale la pena leer:

Otros consejos

Suponga que tiene una lista:

List<Stuff1> l = // get from somewhere

Ahora intenta:

AllDoSomething(l);

Con la versión genérica, se permitirá. Con el no genérico, no lo hará. Esa es la diferencia esencial. Una lista de Stuff1 no es una lista de StuffBase. Pero en el caso genérico, no es necesario que sea exactamente una lista de <=>, por lo que es más flexible.

Puede solucionarlo copiando primero su lista de <=> en una lista de <=>, para que sea compatible con la versión no genérica. Pero luego suponga que tiene un método:

List<T> TransformList<T>(List<T> input) where T : StuffBase
{
    List<T> output = new List<T>();

    foreach (T item in input)
    {
        // examine item and decide whether to discard it,
        // make new items, whatever
    }

    return output;
}

Sin genéricos, podría aceptar una lista de <=>, pero luego tendría que devolver una lista de <=>. La persona que llama tendría que usar yesos si supiera que los artículos son realmente de un tipo derivado. Por lo tanto, los genéricos le permiten preservar el tipo real de un argumento y canalizarlo a través del método hasta el tipo de retorno.

En el ejemplo que proporcionó no hay diferencia, pero intente lo siguiente:

List<Stuff1> items = new List<Stuff1>();
items.Add(new Stuff1());
AllDoSomething(items);
AllDoSomething<StuffBase>(items);

La primera llamada funciona bien pero la segunda no se compila debido a la covarianza genérica

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top