Sintaxe mais curta para transmitir de uma Lista <X> para uma Lista <Y>?

237

Eu sei que é possível converter uma lista de itens de um tipo para outro (considerando que seu objeto tem um método de operador explícito estático público para fazer a conversão), um de cada vez, da seguinte maneira:

List<Y> ListOfY = new List<Y>();

foreach(X x in ListOfX)
    ListOfY.Add((Y)x);

Mas não é possível transmitir a lista inteira de uma só vez? Por exemplo,

ListOfY = (List<Y>)ListOfX;
Jimbo
fonte
@Oded: Eu apenas tentei deixar isso um pouco mais claro. Não se preocupe, você não está, eu entendo :)
BoltClock
1
Supondo que X deriva de Y e Z deriva de Y, pense no que aconteceria se você adicionasse Z à sua Lista <Y>, que é realmente uma Lista <X>.
Richard Friend

Respostas:

497

Se Xrealmente pode ser transmitido para Yvocê, você deve poder usar

List<Y> listOfY = listOfX.Cast<Y>().ToList();

Algumas coisas para estar ciente (H / T para comentaristas!)

Jamiec
fonte
12
Tenha outro crachá de ouro. Isso foi bastante útil.
precisa saber é
6
Deve incluir a seguinte linha para que o compilador reconheça esses métodos de extensão: using System.Linq;
Hypehuman
8
Lembre-se também de que, apesar de converter cada item da lista, a lista em si não é lançada; em vez disso, uma nova lista é criada com o tipo desejado.
Hypehuman
4
Lembre-se também de que o Cast<T>método não suporta operadores de conversão personalizados. Por que o Linq Cast Helper não funciona com o operador implícito de conversão .
ClDay
Ele não funciona para um objeto que tem um método explícito operador (quadro 4.0)
Adrian
100

A conversão direta var ListOfY = (List<Y>)ListOfXnão é possível porque exigiria co-contravariância do List<T>tipo e isso simplesmente não pode ser garantido em todos os casos. Leia para ver as soluções para esse problema de fundição.

Embora pareça normal ser capaz de escrever código como este:

List<Animal> animals = (List<Animal>) mammalList;

porque podemos garantir que todo mamífero será um animal, isso é obviamente um erro:

List<Mammal> mammals = (List<Mammal>) animalList;

já que nem todo animal é um mamífero.

No entanto, usando C # 3 e acima, você pode usar

IEnumerable<Animal> animals = mammalList.Cast<Animal>();

isso facilita um pouco o elenco. Isso é sintaticamente equivalente ao seu código de adição um por um, pois usa uma conversão explícita para converter cada um Mammalda lista em uma Animale falhará se a conversão não for bem-sucedida.

Se você quiser ter mais controle sobre o processo de conversão / conversão, use o ConvertAllmétodo da List<T>classe, que pode usar uma expressão fornecida para converter os itens. Ele tem o benefício adicional de retornar a List, em vez de IEnumerable, portanto não .ToList()é necessário.

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
SWeko
fonte
2
Não acredito que nunca marquei esta resposta com +1 até agora. É muito melhor do que o meu acima.
21714 Jamiec
6
@ Jamiec Eu não marquei +1 porque ele começa com "Não, não é possível", enquanto esconde a resposta que muitos que acham esta pergunta estão procurando. Tecnicamente, ele respondeu à pergunta do OP mais completamente.
21119 Dan Bechard
13

Para adicionar ao ponto de Sweko:

A razão pela qual o elenco

var listOfX = new List<X>();
ListOf<Y> ys = (List<Y>)listOfX; // Compile error: Cannot implicitly cast X to Y

isso não é possível porque List<T>é invariável no Tipo T e, portanto, não importa se Xderiva de Y) - isso ocorre porque List<T>é definido como:

public class List<T> : IList<T>, ICollection<T>, IEnumerable<T> ... // Other interfaces

(Observe que nesta declaração, digite Taqui não possui modificadores de variação adicionais)

No entanto, se coleções mutáveis ​​não forem necessárias em seu design, é possível fazer upcast em muitas das coleções imutáveis , por exemplo, desde que Giraffederivadas de Animal:

IEnumerable<Animal> animals = giraffes;

Isso ocorre porque IEnumerable<T>suporta covariância em T- isso faz sentido, pois IEnumerableimplica que a coleção não pode ser alterada, pois não possui suporte para métodos para adicionar ou remover elementos da coleção. Observe a outpalavra - chave na declaração de IEnumerable<T>:

public interface IEnumerable<out T> : IEnumerable

( Aqui está mais uma explicação do motivo pelo qual coleções mutáveis ​​como Listnão podem suportar covariance, enquanto iteradores e coleções imutáveis ​​podem.)

Fundição com .Cast<T>()

Como outros já mencionaram, .Cast<T>()pode ser aplicado a uma coleção para projetar uma nova coleção de elementos convertidos para T, no entanto, isso será acionado InvalidCastExceptionse a conversão em um ou mais elementos não for possível (o que seria o mesmo comportamento que o explícito no foreachloop do OP ).

Filtragem e conversão com OfType<T>()

Se a lista de entrada contiver elementos de tipos diferentes e incompatíveis, o potencial InvalidCastExceptionpoderá ser evitado usando em .OfType<T>()vez de .Cast<T>(). ( .OfType<>()verifica se um elemento pode ser convertido no tipo de destino, antes de tentar a conversão, e filtra tipos incompatíveis.)

para cada

Observe também que, se o OP tivesse escrito isso: (observe o explícitoY y no foreach)

List<Y> ListOfY = new List<Y>();

foreach(Y y in ListOfX)
{
    ListOfY.Add(y);
}

que o elenco também será tentado. No entanto, se nenhuma conversão for possível, InvalidCastExceptionhaverá um resultado.

Exemplos

Por exemplo, dada a hierarquia de classes simples (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) { }
}

Ao trabalhar com uma coleção de tipos mistos:

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();

Enquanto que:

var castedAnimals = mixedAnimals.OfType<Elephant>()
    .ToList();
// Ellie

filtra apenas os elefantes - ou seja, as zebras são eliminadas.

Operadores implícitos de elenco

Sem dinâmica, os operadores de conversão definidos pelo usuário são usados ​​apenas em tempo de compilação *; portanto, mesmo que um operador de conversão entre o Zebra e o Elephant seja disponibilizado, o comportamento do tempo de execução acima das abordagens à conversão não mudaria.

Se adicionarmos um operador de conversão para converter uma Zebra em um elefante:

public class Zebra : Animal
{
    public Zebra(string name) : base(name) { }
    public static implicit operator Elephant(Zebra z)
    {
        return new Elephant(z.Name);
    }
}

Em vez disso, dado o operador de conversão acima, o compilador poderá alterar o tipo da matriz abaixo de Animal[]para Elephant[], pois as Zebras agora podem ser convertidas em uma coleção homogênea de elefantes:

var compilerInferredAnimals = new []
{
    new Zebra("Zed"),
    new Elephant("Ellie")
};

Usando operadores de conversão implícitos em tempo de execução

* Conforme mencionado por Eric, no entanto, o operador de conversão pode ser acessado em tempo de execução, recorrendo 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
StuartLC
fonte
Ei, eu apenas tentei o exemplo "Usando foreach () para filtragem de tipo" usando: var list = new List <object> () {1, "a", 2, "b", 3, "c", 4 " d "}; foreach (int i na lista) Console.WriteLine (i); e quando o executo, recebo "O elenco especificado não é válido". Estou esquecendo de algo? Eu não acho que foreach funcionou dessa maneira, e é por isso que eu estava tentando.
Brent Rittenhouse
Além disso, não é uma coisa de referência versus tipo de valor. Eu apenas tentei com uma classe base de 'Coisa' e duas classes derivadas: 'Pessoa' e 'Animal'. Quando faço a mesma coisa, recebo: "Não é possível converter o objeto do tipo 'Animal' para o tipo 'Pessoa'". Portanto, é definitivamente iterativo através de cada elemento. Se eu fizesse um OfType na lista, ele funcionaria. O ForEach provavelmente seria muito lento se tivesse que verificar isso, a menos que o compilador o otimizasse.
Brent Rittenhouse
Obrigado Brent - eu estava fora do curso por lá. foreachnão filtra, mas usar um tipo mais derivado como variável de iteração forçará o compilador a tentar um Cast, que falhará no primeiro elemento que não estiver em conformidade.
StuartLC
7

Você pode usar List<Y>.ConvertAll<T>([Converter from Y to T]);

Andrey
fonte
3

Esta não é a resposta a esta pergunta, mas pode ser útil para alguns: como disse @SWeko, graças a covariância e contravariance, List<X>não pode ser convertido em List<Y>, mas List<X>pode ser lançado IEnumerable<Y>, e mesmo com conversão implícita.

Exemplo:

List<Y> ListOfY = new List<Y>();
List<X> ListOfX = (List<X>)ListOfY; // Compile error

mas

List<Y> ListOfY = new List<Y>();
IEnumerable<X> EnumerableOfX = ListOfY;  // No issue

A grande vantagem é que ele não cria uma nova lista na memória.

Xav987
fonte
1
Eu gosto disso porque se você tem uma grande lista de fontes, não há desempenho atingido no início. Em vez disso, há uma pequena conversão não perceptível para cada entrada sendo processada pelo destinatário. Também não há muita memória acumulada. perfeito para processar fluxos.
Johan Franzén
-2
dynamic data = List<x> val;  
List<y> val2 = ((IEnumerable)data).Cast<y>().ToList();
user2361134
fonte