¿Cuándo debería o no debería usar restricciones de tipo genérico?
-
10-07-2019 - |
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.
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:
- http://www.pabich.eu/blog/archive/2008/02/12/c-generics---parameter-variance-its-constraints-and-how-it.aspx
- http://blogs.msdn.com/rmbyers /archive/2006/06/01/613690.aspx
- http://msdn.microsoft.com/ es-es / library / ms228359 (VS.80) .aspx
- http://www.csharp411.com/convert-between-generic- ienumerablet /
- http://research.microsoft.com/apps/pubs /default.aspx?id=64042
- ¿Por qué no se puede enumerar < parent > = Lista & Lt; child & Gt ;?
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