c# - cast - ¿Sintaxis más corta para lanzar desde una Lista<X> a una Lista<Y>?
ienumerable c# (4)
El lanzamiento directo var ListOfY = (List<Y>)ListOfX
no es posible porque requeriría co/contravariance del tipo List<T>
, y eso simplemente no se puede garantizar en todos los casos. Por favor, sigue leyendo para ver las soluciones a este problema de fundición.
Si bien parece normal poder escribir código como este:
List<Animal> animals = (List<Animal>) mammalList;
porque podemos garantizar que cada mamífero será un animal, obviamente esto es un error:
List<Mammal> mammals = (List<Mammal>) animalList;
ya que no todos los animales son mamíferos.
Sin embargo, usando C # 3 y superior, puede usar
IEnumerable<Animal> animals = mammalList.Cast<Animal>();
eso facilita el casting un poco. Esto es sintácticamente equivalente a su código de adición uno por uno, ya que usa un lanzamiento explícito para convertir cada Mammal
en la lista a un Animal
, y fallará si el lanzamiento no es exitoso.
Si desea tener más control sobre el proceso de conversión / conversión, puede usar el método ConvertAll
de la clase List<T>
, que puede usar una expresión proporcionada para convertir los elementos. Tiene la ventaja adicional de que devuelve una List
, en lugar de IEnumerable
, por lo que no es necesario .ToList()
.
List<object> o = new List<object>();
o.Add("one");
o.Add("two");
o.Add(3);
IEnumerable<string> s1 = o.Cast<string>(); //fails on the 3rd item
List<string> s2 = o.ConvertAll(x => x.ToString()); //succeeds
Sé que es posible emitir una lista de elementos de un tipo a otro (dado que su objeto tiene un método de operador explícito estático público para hacer el casting) uno a la vez de la siguiente manera:
List<Y> ListOfY = new List<Y>();
foreach(X x in ListOfX)
ListOfY.Add((Y)x);
¿Pero no es posible emitir toda la lista al mismo tiempo? Por ejemplo,
ListOfY = (List<Y>)ListOfX;
Puede usar List.ConvertAll ([Converter from Y to T]);
Si X
realmente se puede convertir a Y
, deberías poder usar
List<Y> listOfY = listOfX.Cast<Y>().ToList();
Algunas cosas que debe tener en cuenta (¡H / T para los comentaristas!)
- Debe incluir el
using System.Linq;
para obtener este método de extensión - Esto arroja cada elemento en la lista, no la lista en sí misma. Se creará una nueva
List<Y>
mediante la llamada aToList()
. - Este método no admite operadores de conversión personalizados. (ver http://.com/questions/14523530/why-does-the-linq-cast-helper-not-work-with-the-implicit-cast-operator )
- Este método no funciona para un objeto que tiene un método de operador explícito (framework 4.0)
Para agregar al punto de Sweko:
La razón por la cual el reparto
var listOfX = new List<X>();
ListOf<Y> ys = (List<Y>)listOfX; // Compile error: Cannot implicitly cast X to Y
no es posible es porque la List<T>
es invariante en el Tipo T y, por lo tanto, no importa si X
deriva de Y
), esto es porque la List<T>
se define como:
public class List<T> : IList<T>, ICollection<T>, IEnumerable<T> ... // Other interfaces
(Tenga en cuenta que en esta declaración, escriba T
aquí no tiene modificadores de varianza adicionales)
Sin embargo, si no se requieren colecciones mutables en su diseño, es posible un upcast en muchas de las colecciones inmutables, por ejemplo, siempre que Giraffe
derive de Animal
:
IEnumerable<Animal> animals = giraffes;
Esto se debe a que IEnumerable<T>
admite covarianza en T
; esto tiene sentido dado que IEnumerable
implica que la colección no se puede cambiar, ya que no admite métodos para Agregar o Eliminar elementos de la colección. Tenga en cuenta la palabra clave out
en la declaración de IEnumerable<T>
:
public interface IEnumerable<out T> : IEnumerable
( Aquí hay más explicaciones sobre la razón por la cual las colecciones mutables como List
no pueden soportar la covariance
, mientras que las iteraciones y colecciones inmutables sí pueden).
Casting con .Cast<T>()
Como otros han mencionado, .Cast<T>()
se puede aplicar a una colección para proyectar una nueva colección de elementos convertidos a T, sin embargo, esto arrojará una InvalidCastException
si el lanzamiento en uno o más elementos no es posible (lo cual ser el mismo comportamiento que hacer el lanzamiento explícito en el bucle foreach
del OP).
Filtrado y lanzamiento con OfType<T>()
Si la lista de entrada contiene elementos de diferentes tipos incompatibles, la posible InvalidCastException
se puede evitar utilizando .OfType<T>()
lugar de .Cast<T>()
. ( .OfType<>()
comprueba si un elemento se puede convertir al tipo de destino, antes de intentar la conversión, y filtra los tipos incompatibles).
Uso de foreach () para el filtrado de tipo
También tenga en cuenta que si el OP hubiera escrito esto en su lugar: (tenga en cuenta el Y y
explícito en el foreach
)
List<Y> ListOfY = new List<Y>();
foreach(Y y in ListOfX)
{
ListOfY.Add(y);
}
que cualquier elemento que no sea Y
o que no pueda convertirse en Y
se omitirá y eliminará de la lista resultante. es decir, foreach(Y y in ListOfX){ ... Add(y) }
es equivalente a ListOfX.OfType<Y>()
Ejemplos
Por ejemplo, dada la jerarquía de clases simple (C # 6):
public abstract class Animal
{
public string Name { get; }
protected Animal(string name) { Name = name; }
}
public class Elephant : Animal
{
public Elephant(string name) : base(name){}
}
public class Zebra : Animal
{
public Zebra(string name) : base(name) { }
}
Cuando se trabaja con una colección de tipos mixtos:
var mixedAnimals = new Animal[]
{
new Zebra("Zed"),
new Elephant("Ellie")
};
foreach(Animal animal in mixedAnimals)
{
// Fails for Zed - `InvalidCastException - cannot cast from Zebra to Elephant`
castedAnimals.Add((Elephant)animal);
}
var castedAnimals = mixedAnimals.Cast<Elephant>()
// Also fails for Zed with `InvalidCastException
.ToList();
Mientras:
foreach(Elephant animal in mixedAnimals)
{
castedAnimals.Add(animal);
}
// Ellie
y
var castedAnimals = mixedAnimals.OfType<Elephant>()
.ToList();
// Ellie
Ambos enfoques filtran solo a los Elefantes, es decir, las cebras se eliminan.
Re: operadores implícitos de molde
Sin dinámica, los operadores de conversión definidos por el usuario solo se utilizan en compile-time *, por lo que incluso si un operador de conversión entre Zebra y Elephant estuviera disponible, el comportamiento del tiempo de ejecución anterior de los enfoques de conversión no cambiaría.
Si agregamos un operador de conversión para convertir un Zebra en un Elefante:
public class Zebra : Animal
{
public Zebra(string name) : base(name) { }
public static implicit operator Elephant(Zebra z)
{
return new Elephant(z.Name);
}
}
En cambio, dado el operador de conversión anterior, el compilador podrá cambiar el tipo de la matriz siguiente de Animal[]
a Elephant[]
, dado que las cebras ahora se pueden convertir en una colección homogénea de elefantes:
var compilerInferredAnimals = new []
{
new Zebra("Zed"),
new Elephant("Ellie")
};
Uso de operadores de conversión implícita en tiempo de ejecución
* Como menciona Eric, sin embargo se puede acceder al operador de conversión en tiempo de ejecución recurriendo a dynamic
:
var mixedAnimals = new Animal[] // i.e. Polymorphic collection
{
new Zebra("Zed"),
new Elephant("Ellie")
};
foreach (dynamic animal in mixedAnimals)
{
castedAnimals.Add(animal);
}
// Returns Zed, Ellie