Si bien podemos heredar de la clase / interfaz base, ¿por qué no podemos declarar un List<>
uso de la misma clase / interfaz?
interface A
{ }
class B : A
{ }
class C : B
{ }
class Test
{
static void Main(string[] args)
{
A a = new C(); // OK
List<A> listOfA = new List<C>(); // compiler Error
}
}
¿Hay alguna forma de evitarlo?
Respuestas:
La forma de hacer que esto funcione es iterar sobre la lista y emitir los elementos. Esto se puede hacer usando ConvertAll:
También puedes usar Linq:
fuente
ConvertAll
oCast
?En primer lugar, deje de usar nombres de clase imposibles de entender como A, B, C. Use Animal, Mamífero, Jirafa, o Comida, Fruta, Naranja o algo donde las relaciones sean claras.
Su pregunta es "¿por qué no puedo asignar una lista de jirafas a una variable de tipo lista de animales, ya que puedo asignar una jirafa a una variable de tipo animal?"
La respuesta es: suponga que podría. ¿Qué podría salir mal entonces?
Bueno, puedes agregar un tigre a una lista de animales. Supongamos que le permitimos poner una lista de jirafas en una variable que contiene una lista de animales. Luego intenta agregar un tigre a esa lista. ¿Lo que pasa? ¿Quieres que la lista de jirafas contenga un tigre? ¿Quieres un choque? ¿o quieres que el compilador te proteja del accidente al hacer que la asignación sea ilegal en primer lugar?
Elegimos este último.
Este tipo de conversión se denomina conversión "covariante". En C # 4, le permitiremos realizar conversiones covariantes en interfaces y delegados cuando se sabe que la conversión es siempre segura . Vea los artículos de mi blog sobre covarianza y contravarianza para más detalles. (Habrá uno nuevo sobre este tema tanto el lunes como el jueves de esta semana).
fuente
IEnumerable
lugar de unList
? es decir:List<Animal> listAnimals = listGiraffes as List<Animal>;
no es posible, peroIEnumerable<Animal> eAnimals = listGiraffes as IEnumerable<Animal>
funciona.IEnumerable<T>
yIEnumerator<T>
ambos están marcados como seguros para la covarianza, y el compilador lo ha verificado.Para citar la gran explicación de Eric
Pero, ¿qué sucede si desea elegir un bloqueo en tiempo de ejecución en lugar de un error de compilación? Normalmente usaría Cast <> o ConvertAll <> pero luego tendrá 2 problemas: creará una copia de la lista. Si agrega o elimina algo en la nueva lista, esto no se reflejará en la lista original. Y en segundo lugar, hay una gran penalización de rendimiento y memoria ya que crea una nueva lista con los objetos existentes.
Tuve el mismo problema y, por lo tanto, creé una clase de contenedor que puede emitir una lista genérica sin crear una lista completamente nueva.
En la pregunta original, podría usar:
y aquí la clase wrapper (+ un método de extensión CastList para un uso fácil)
fuente
Si lo usa
IEnumerable
, funcionará (al menos en C # 4.0, no he probado versiones anteriores). Esto es solo un reparto, por supuesto, seguirá siendo una lista.En vez de -
List<A> listOfA = new List<C>(); // compiler Error
En el código original de la pregunta, use -
IEnumerable<A> listOfA = new List<C>(); // compiler error - no more! :)
fuente
List<A> listOfA = new List<C>(); // compiler Error
en el código original de la pregunta, ingreseIEnumerable<A> listOfA = new List<C>(); // compiler error - no more! :)
En cuanto a por qué no funciona, podría ser útil comprender la covarianza y la contravarianza .
Solo para mostrar por qué esto no debería funcionar, aquí hay un cambio en el código que proporcionó:
¿Debería funcionar esto? El primer elemento de la lista es del tipo "B", pero el tipo del elemento DerivedList es C.
Ahora, suponga que realmente solo queremos hacer una función genérica que opere en una lista de algún tipo que implemente A, pero no nos importa de qué tipo sea:
fuente
Solo puedes enviar a listas de solo lectura. Por ejemplo:
Y no puede hacerlo para listas que admiten elementos de guardado. La razón por la cual es:
¿Ahora que? Recuerde que listObject y listString son la misma lista en realidad, por lo que listString ahora tiene un elemento objeto: no debería ser posible y no lo es.
fuente
Personalmente me gusta crear librerías con extensiones a las clases.
fuente
Porque C # no permite ese tipo de
herenciaconversión en este momento .fuente
Esta es una extensión de la brillante respuesta de BigJim .
En mi caso, tuve una
NodeBase
clase con unChildren
diccionario y necesitaba una forma genérica de hacer búsquedas O (1) de los niños. Intentaba devolver un campo de diccionario privado en el captador deChildren
, así que obviamente quería evitar las costosas copias / iteraciones. Por lo tanto, utilicé el código de Bigjim para convertirloDictionary<whatever specific type>
en un genéricoDictionary<NodeBase>
:Esto funcionó bien. Sin embargo, eventualmente encontré limitaciones no relacionadas y terminé creando un
FindChild()
método abstracto en la clase base que haría las búsquedas en su lugar. Al final resultó que esto eliminó la necesidad del diccionario fundido en primer lugar. (Pude reemplazarlo con un simpleIEnumerable
para mis propósitos).Entonces, la pregunta que puede hacer (especialmente si el rendimiento es un problema que le prohíbe usar
.Cast<>
o.ConvertAll<>
) es:"¿Realmente necesito lanzar la colección completa, o puedo usar un método abstracto para mantener el conocimiento especial necesario para realizar la tarea y así evitar el acceso directo a la colección?"
A veces la solución más simple es la mejor.
fuente
También puede usar el
System.Runtime.CompilerServices.Unsafe
paquete NuGet para crear una referencia al mismoList
:Dado el ejemplo anterior, puede acceder a las
Hammer
instancias existentes en la lista utilizando latools
variable. AgregarTool
instancias a la lista genera unaArrayTypeMismatchException
excepción porque hacetools
referencia a la misma variable quehammers
.fuente
He leído todo este hilo, y solo quiero señalar lo que me parece una inconsistencia.
El compilador le impide realizar la tarea con Listas:
Pero el compilador está perfectamente bien con las matrices:
El argumento sobre si se sabe que la asignación es segura se desmorona aquí. La asignación que hice con la matriz no es segura . Para probar eso, si sigo con esto:
Recibo una excepción de tiempo de ejecución "ArrayTypeMismatchException". ¿Cómo se explica esto? Si el compilador realmente quiere evitar que haga algo estúpido, debería haberme impedido hacer la asignación de la matriz.
fuente