c# - Convertir lista<DerivedClass> en List<BaseClass>
inheritance collections (9)
Si bien podemos heredar de la clase base / interfaz, ¿por qué no podemos declarar una List<>
usando 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 una forma de evitarlo?
En cuanto a por qué no funciona, podría ser útil comprender la covarianza y la contradicción .
Solo para mostrar por qué esto no debería funcionar, aquí hay un cambio al código que proporcionó:
void DoesThisWork()
{
List<C> DerivedList = new List<C>();
List<A> BaseList = DerivedList;
BaseList.Add(new B());
C FirstItem = DerivedList.First();
}
¿Debería funcionar esto? El primer elemento de la lista es del tipo "B", pero el tipo del elemento DerivedList es C.
Ahora, supongamos 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 qué tipo sea:
void ThisWorks<T>(List<T> GenericList) where T:A
{
}
void Test()
{
ThisWorks(new List<B>());
ThisWorks(new List<C>());
}
En primer lugar, deje de usar nombres de clases 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 entonces 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: supongamos que pudieras. ¿Qué podría salir mal?
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 haciendo 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 las interfaces y delegar 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).
Esta es una extensión de la brillante answer BigJim.
En mi caso, tenía una clase NodeBase
con un diccionario para Children
, y necesitaba una forma de hacer búsquedas O (1) genéricamente de los niños. Estaba intentando devolver un campo de diccionario privado en el captador de Children
, así que obviamente quería evitar la costosa copia / iteración. Por lo tanto, utilicé el código de Bigjim para convertir el Dictionary<whatever specific type>
en un Dictionary<NodeBase>
genérico Dictionary<NodeBase>
:
// Abstract parent class
public abstract class NodeBase
{
public abstract IDictionary<string, NodeBase> Children { get; }
...
}
// Implementing child class
public class RealNode : NodeBase
{
private Dictionary<string, RealNode> containedNodes;
public override IDictionary<string, NodeBase> Children
{
// Using a modification of Bigjim''s code to cast the Dictionary:
return new IDictionary<string, NodeBase>().CastDictionary<string, RealNode, NodeBase>();
}
...
}
Esto funcionó bien. Sin embargo, finalmente encontré limitaciones no relacionadas y terminé creando un FindChild()
abstracto FindChild()
en la clase base que haría las búsquedas en su lugar. Resultó que esto eliminó la necesidad del diccionario en castellano en primer lugar. (Pude reemplazarlo con un simple IEnumerable
para mis propósitos).
Así que la pregunta que puede hacer (especialmente si el rendimiento es un problema que le prohíbe usar .Cast<>
o .ConvertAll<>
) es:
"¿De verdad necesito lanzar toda la colección, 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.
La forma de hacer que esto funcione es iterar sobre la lista y lanzar los elementos. Esto se puede hacer usando ConvertAll:
List<A> listOfA = new List<C>().ConvertAll(x => (A)x);
También puedes usar Linq:
List<A> listOfA = new List<C>().Cast<A>().ToList();
Para citar la gran explicación de Eric
¿Lo que pasa? ¿Quieres que la lista de jirafas contenga un tigre? ¿Quieres un choque? ¿o quieres que el compilador te proteja del accidente haciendo que la asignación sea ilegal en primer lugar? Elegimos este último
Pero, ¿qué ocurre 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ía 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, existe 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 contenedora que puede generar una lista genérica sin crear una lista completamente nueva.
En la pregunta original, podrías usar:
class Test
{
static void Main(string[] args)
{
A a = new C(); // OK
IList<A> listOfA = new List<C>().CastList<C,A>(); // now ok!
}
}
y aquí la clase contenedora (+ un método de extensión CastList para facilitar el uso)
public class CastedList<TTo, TFrom> : IList<TTo>
{
public IList<TFrom> BaseList;
public CastedList(IList<TFrom> baseList)
{
BaseList = baseList;
}
// IEnumerable
IEnumerator IEnumerable.GetEnumerator() { return BaseList.GetEnumerator(); }
// IEnumerable<>
public IEnumerator<TTo> GetEnumerator() { return new CastedEnumerator<TTo, TFrom>(BaseList.GetEnumerator()); }
// ICollection
public int Count { get { return BaseList.Count; } }
public bool IsReadOnly { get { return BaseList.IsReadOnly; } }
public void Add(TTo item) { BaseList.Add((TFrom)(object)item); }
public void Clear() { BaseList.Clear(); }
public bool Contains(TTo item) { return BaseList.Contains((TFrom)(object)item); }
public void CopyTo(TTo[] array, int arrayIndex) { BaseList.CopyTo((TFrom[])(object)array, arrayIndex); }
public bool Remove(TTo item) { return BaseList.Remove((TFrom)(object)item); }
// IList
public TTo this[int index]
{
get { return (TTo)(object)BaseList[index]; }
set { BaseList[index] = (TFrom)(object)value; }
}
public int IndexOf(TTo item) { return BaseList.IndexOf((TFrom)(object)item); }
public void Insert(int index, TTo item) { BaseList.Insert(index, (TFrom)(object)item); }
public void RemoveAt(int index) { BaseList.RemoveAt(index); }
}
public class CastedEnumerator<TTo, TFrom> : IEnumerator<TTo>
{
public IEnumerator<TFrom> BaseEnumerator;
public CastedEnumerator(IEnumerator<TFrom> baseEnumerator)
{
BaseEnumerator = baseEnumerator;
}
// IDisposable
public void Dispose() { BaseEnumerator.Dispose(); }
// IEnumerator
object IEnumerator.Current { get { return BaseEnumerator.Current; } }
public bool MoveNext() { return BaseEnumerator.MoveNext(); }
public void Reset() { BaseEnumerator.Reset(); }
// IEnumerator<>
public TTo Current { get { return (TTo)(object)BaseEnumerator.Current; } }
}
public static class ListExtensions
{
public static IList<TTo> CastList<TFrom, TTo>(this IList<TFrom> list)
{
return new CastedList<TTo, TFrom>(list);
}
}
Personalmente me gusta crear libs con extensiones a las clases
public static List<TTo> Cast<TFrom, TTo>(List<TFrom> fromlist)
where TFrom : class
where TTo : class
{
return fromlist.ConvertAll(x => x as TTo);
}
Porque C # no permite ese tipo de herencia conversión en este momento .
Si usa IEnumerable
en IEnumerable
lugar, funcionará (al menos en C # 4.0, no he probado versiones anteriores). Esto es solo un elenco, por supuesto, seguirá siendo una lista.
En lugar 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! :)
Solo puedes enviar contenido a listas de solo lectura. Por ejemplo:
IEnumerable<A> enumOfA = new List<C>();//This works
IReadOnlyCollection<A> ro_colOfA = new List<C>();//This works
IReadOnlyList<A> ro_listOfA = new List<C>();//This works
Y no puede hacerlo para listas que admiten elementos de guardado. La razón por la cual es:
List<string> listString=new List<string>();
List<object> listObject=(List<object>)listString;//Assume that this is possible
listObject.Add(new object());
¿Ahora que? Recuerde que listObject y listString son en realidad la misma lista, por lo que listString ahora tiene un elemento object, no debería ser posible y no lo es.