¿Es mejor usarlo List<string>
en anotaciones de tipo o StringList
dondeStringList
class StringList : List<String> { /* no further code!*/ }
c#
inheritance
Ruudjah
fuente
fuente
StringList
yList<string>
? No hay ninguno, sin embargo, usted (el "usted" genérico) logró agregar otro detalle a la base del código que es único solo para su proyecto y no significa nada para nadie más hasta después de haber estudiado el código. ¿Por qué enmascarar el nombre de una lista de cadenas solo para seguir llamándola una lista de cadenas? Por otro lado, si quisieras hacerloBagOBagels : List<string>
, creo que tiene más sentido. Puede repetirlo como IEnumerable, los consumidores externos no se preocupan por la implementación: bondad en general.Respuestas:
Hay otra razón por la que es posible que desee heredar de un tipo genérico.
Microsoft recomienda evitar anidar tipos genéricos en las firmas de métodos .
Es una buena idea, entonces, crear tipos con nombre de dominio comercial para algunos de sus genéricos. En lugar de tener un
IEnumerable<IDictionary<string, MyClass>>
, crea un tipoMyDictionary : IDictionary<string, MyClass>
y luego tu miembro se convierte en unIEnumerable<MyDictionary>
, que es mucho más legible. También le permite usar MyDictionary en varios lugares, lo que aumenta la explicidad de su código.Esto puede no parecer un gran beneficio, pero en el código comercial del mundo real he visto genéricos que te pondrán los pelos de punta. Cosas como
List<Tuple<string, Dictionary<int, List<Dictionary<string, List<double>>>>>
. El significado de ese genérico no es de ninguna manera obvio. Sin embargo, si se construyera con una serie de tipos creados explícitamente, la intención del desarrollador original probablemente sería mucho más clara. Aún más, si ese tipo genérico necesita cambiar por alguna razón, encontrar y reemplazar todas las instancias de ese tipo genérico podría ser un verdadero dolor, en comparación con cambiarlo en la clase derivada.Finalmente, hay otra razón por la cual alguien podría desear crear sus propios tipos derivados de genéricos. Considere las siguientes clases:
Ahora, ¿cómo sabemos qué
ICollection<MyItems>
pasar al constructor de MyConsumerClass? Si creamos las clases derivadasclass MyItems : ICollection<MyItems>
yclass MyItemCache : ICollection<MyItems>
, a continuación, MyConsumerClass puede ser muy específico acerca de cuál de las dos colecciones que realmente quiere. Esto funciona muy bien con un contenedor de IoC, que puede resolver las dos colecciones muy fácilmente. También permite que el programador sea más preciso: si tiene sentidoMyConsumerClass
trabajar con cualquier ICollection, puede tomar el constructor genérico. Sin embargo, si nunca tiene sentido comercial utilizar uno de los dos tipos derivados, el desarrollador puede restringir el parámetro constructor al tipo específico.En resumen, a menudo vale la pena derivar tipos de tipos genéricos: permite un código más explícito cuando sea apropiado y ayuda a la legibilidad y mantenibilidad.
fuente
En principio, no hay diferencia entre heredar de tipos genéricos y heredar de cualquier otra cosa.
Sin embargo, ¿por qué se derivaría de alguna clase y no agregaría nada más?
fuente
No sé C # muy bien, pero creo que esta es la única manera de poner en práctica una
typedef
, que los programadores de C ++ suelen utilizar por varias razones:Básicamente descartaron la última ventaja al elegir nombres aburridos y genéricos, pero las otras dos razones siguen vigentes.
fuente
StringList
es unaList<string>
- que pueden utilizan indistintamente. Esto es importante cuando se trabaja con otras API (o cuando la exposición de su API), ya que todos los demás en los usos del mundoList<string>
.using StringList = List<string>;
es el más cercano equivalente C # de un typedef. Crear subclases de esta manera evitará el uso de ningún constructor con argumentos.using
restringiéndola al archivo de código fuente dondeusing
se ubica. Desde este punto de vista, la herencia está más cercatypedef
. Y crear una subclase no evita que uno agregue todos los constructores necesarios (aunque puede ser tedioso).using
no se puede usar para este propósito, pero la herencia sí. Esto muestra por qué no estoy de acuerdo con el comentario votado de Pete Kirkham: no lo considerousing
una alternativa real a C ++typedef
.Se me ocurre al menos una razón práctica.
Declarar tipos no genéricos que heredan de tipos genéricos (incluso sin agregar nada), permite que esos tipos sean referenciados desde lugares donde de otra manera no estarían disponibles, o disponibles de una manera más complicada de lo que deberían ser.
Uno de esos casos es cuando se agregan configuraciones a través del editor de configuraciones en Visual Studio, donde solo los tipos no genéricos parecen estar disponibles. Si va allí, notará que todas las
System.Collections
clases están disponibles, pero noSystem.Collections.Generic
aparecen colecciones según corresponda.Otro caso es cuando se crean instancias de tipos a través de XAML en WPF. No hay soporte para pasar argumentos de tipo en la sintaxis XML cuando se usa un esquema anterior a 2009. Esto empeora por el hecho de que el esquema de 2006 parece ser el predeterminado para los nuevos proyectos de WPF.
fuente
Creo que necesitas investigar un poco de historia .
De lo contrario, Theodoros Chatzigiannakis tuvo las mejores respuestas, por ejemplo, todavía hay muchas herramientas que no entienden los tipos genéricos. O tal vez incluso una mezcla de su respuesta e historia.
fuente
En algunos casos es imposible usar tipos genéricos, por ejemplo, no puede hacer referencia a un tipo genérico en XAML. En estos casos, puede crear un tipo no genérico que herede del tipo genérico que originalmente quería usar.
Si es posible, lo evitaría.
A diferencia de un typedef, usted crea un nuevo tipo. Si usa StringList como parámetro de entrada a un método, sus llamantes no pueden pasar a
List<string>
.Además, necesitaría copiar explícitamente todos los constructores que desea utilizar, en el ejemplo de StringList que no pudo decir
new StringList(new[]{"a", "b", "c"})
, aunqueList<T>
define dicho constructor.Editar:
La respuesta aceptada se centra en los profesionales cuando se utilizan los tipos derivados dentro de su modelo de dominio. Estoy de acuerdo en que potencialmente pueden ser útiles en este caso, aún así, personalmente los evitaría incluso allí.
Pero, en mi opinión, nunca debe usarlos en una API pública porque dificulta la vida de la persona que llama o desperdicia el rendimiento (tampoco me gustan las conversiones implícitas en general).
fuente
Por lo que vale, aquí hay un ejemplo de cómo se usa la herencia de una clase genérica en el compilador Roslyn de Microsoft, y sin siquiera cambiar el nombre de la clase. (Estaba tan desconcertado por esto que terminé aquí en mi búsqueda para ver si esto era realmente posible).
En el proyecto CodeAnalysis puedes encontrar esta definición:
Luego, en el proyecto CSharpCodeanalysis existe esta definición:
Esta clase PEModuleBuilder no genérica se usa ampliamente en el proyecto CSharpCodeanalysis, y varias clases en ese proyecto heredan de ella, directa o indirectamente.
Y luego en el proyecto BasicCodeanalysis existe esta definición:
Dado que podemos (con suerte) suponer que Roslyn fue escrita por personas con un amplio conocimiento de C # y cómo debe usarse, creo que esta es una recomendación de la técnica.
fuente
Hay tres posibles razones por las cuales alguien trataría de hacer eso de la cabeza:
1) Para simplificar las declaraciones:
Claro,
StringList
yListString
tienen aproximadamente la misma longitud, pero imagina que estás trabajando con un ejemplo genérico enUserCollection
realidadDictionary<Tuple<string,Type>, IUserData<Dictionary,MySerializer>>
o con otro.2) Para ayudar a AOT:
A veces necesita usar la compilación Ahead Of Time, no Just In Time Compilation, por ejemplo, para iOS. A veces, el compilador AOT tiene dificultades para descubrir todos los tipos genéricos con anticipación, y esto podría ser un intento de proporcionarle una pista.
3) Para agregar funcionalidad o eliminar dependencia:
Tal vez tienen una funcionalidad específica que quieren poner en práctica para
StringList
(podría estar oculto en un método de extensión, no se ha hecho, o histórico) que, o bien no se pueden añadir a laList<string>
sin heredar de ella, o que no quieren contaminarList<string>
con .Alternativamente, pueden desear cambiar la implementación de
StringList
abajo de la pista, por lo que acaban de usar la clase como marcador por ahora (por lo general, haría / usaría interfaces para esto, por ejemploIList
).También señalaría que ha encontrado este código en Irony, que es un analizador de idiomas, un tipo de aplicación bastante inusual. Puede haber alguna otra razón específica por la que se usó esto.
Estos ejemplos demuestran que puede haber razones legítimas para escribir tal declaración, incluso si no es demasiado común. Al igual que con cualquier cosa, considere varias opciones y elija la que sea mejor para usted en ese momento.
Si echa un vistazo al código fuente , parece que la opción 3 es la razón: usan esta técnica para ayudar a agregar funcionalidad / especializar colecciones:
fuente
List<T>
es, pero tienen que inspeccionarStringList
en contexto para comprender realmente lo que es. En realidad, es soloList<string>
, ir por un nombre diferente. Realmente no parece haber ninguna ventaja semántica para hacer esto en un caso tan simple. En realidad, solo está reemplazando un tipo conocido con el mismo tipo con un nombre diferente.Yo diría que no es una buena práctica.
List<string>
comunica "una lista genérica de cadenas". Claramente seList<string>
aplican métodos de extensión. Se requiere menos complejidad para entender; solo se necesita la lista.StringList
comunica "la necesidad de una clase separada". Quizás seList<string>
apliquen métodos de extensión (verifique la implementación del tipo real para esto). Se necesita más complejidad para entender el código; Se requiere una lista y el conocimiento de implementación de clase derivado.fuente
List<string>
.