Более короткий синтаксис для приведения из списка <X> в список <Y>?

237

Я знаю, что можно приводить список элементов из одного типа в другой (учитывая, что у вашего объекта есть публичный статический явный операторный метод для выполнения приведения) по очереди следующим образом:

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

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

Но нельзя ли разыграть весь список за один раз? Например,

ListOfY = (List<Y>)ListOfX;
Джимбо
источник
@Oded: я просто попытался сделать это немного яснее. Не волнуйся, я не понимаю :)
BoltClock
1
Предполагая, что X происходит от Y, а Z - от Y, подумайте, что произойдет, если вы добавите Z в свой список <Y>, который действительно является списком <X>.
Ричард Френд

Ответы:

497

Если Xдействительно может быть приведен к Yвам, должен быть в состоянии использовать

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

Некоторые вещи, которые нужно знать (H / T для комментаторов!)

  • Вы должны включить, using System.Linq;чтобы получить этот метод расширения
  • При этом отбрасывается каждый элемент в списке, а не сам список. Новое List<Y>будет создано при вызове ToList().
  • Этот метод не поддерживает пользовательские операторы преобразования. (см. http://stackoverflow.com/questions/14523530/why-does-the-linq-cast-helper-not-work-with-the-implicit-cast-operator )
  • Этот метод не работает для объекта, у которого есть явный метод оператора (framework 4.0)
Jamiec
источник
12
Есть еще один золотой значок. Это было довольно полезно.
ouflak
6
Нужно включить следующую строку, чтобы компилятор распознал эти методы расширения: using System.Linq;
Гипхуман человек
8
Также имейте в виду, что, хотя это приводит к приведению каждого элемента в списке, сам список не создается; скорее создается новый список с нужным типом.
Гиперман
4
Также имейте в виду, что Cast<T>метод не поддерживает пользовательские операторы преобразования. Почему помощник Linq Cast не работает с неявным оператором Cast .
clD
Он не работает для объекта, у которого есть явный операторный метод (framework 4.0)
Adrian
100

Прямой бросок var ListOfY = (List<Y>)ListOfXне представляется возможным , поскольку для этого потребуется сотрудничество / контравариации в List<T>типа, и что просто не может быть гарантировано в любом случае. Пожалуйста, прочитайте дальше, чтобы увидеть решения этой проблемы.

Хотя кажется нормальным иметь возможность писать такой код:

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

поскольку мы можем гарантировать, что каждое млекопитающее будет животным, это, очевидно, ошибка:

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

Поскольку не каждое животное является млекопитающим.

Однако, используя C # 3 и выше, вы можете использовать

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

это немного облегчает кастинг. Это синтаксически эквивалентно вашему добавляемому по отдельности коду, так как он использует явное приведение для приведения каждого Mammalв списке к Animalи потерпит неудачу, если приведение не будет успешным.

Если вам нравится больше контроля над процессом приведения / преобразования, вы можете использовать ConvertAllметод List<T>класса, который может использовать предоставленное выражение для преобразования элементов. Он имеет дополнительное преимущество, которое он возвращает Listвместо IEnumerable, так что нет .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
SWeko
источник
2
Я не могу поверить, что я никогда не +1 к этому ответу до сих пор. Это намного лучше, чем мой выше.
Jamiec
6
@Jamiec Я не +1, потому что он начинает с «Нет, это невозможно», в то время как ответ на этот вопрос ищут многие, кто ищет этот вопрос. Технически он действительно ответил на вопрос ОП более тщательно.
Дэн Бешард
13

Чтобы добавить к точке Sweko:

Причина, почему актеры

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

невозможно, потому что List<T>является инвариантом в типе T и, следовательно, не имеет значения, является ли оно Xпроизводным от Yэтого - это потому, что List<T>определяется как:

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

(Обратите внимание, что в этом объявлении тип Tздесь не имеет дополнительных модификаторов дисперсии)

Однако, если изменяемые коллекции не требуются в вашем дизайне, в вентиляционных шахтах на многие из неизменных коллекций, возможно , например , при условии , что Giraffeвытекает из Animal:

IEnumerable<Animal> animals = giraffes;

Это потому, что IEnumerable<T>поддерживает ковариацию в T- это имеет смысл, учитывая, что это IEnumerableозначает, что коллекция не может быть изменена, так как она не поддерживает методы для добавления или удаления элементов из коллекции. Обратите внимание на outключевое слово в объявлении IEnumerable<T>:

public interface IEnumerable<out T> : IEnumerable

( Вот дальнейшее объяснение причины, по которой изменяемые коллекции, например, Listне могут поддерживать covariance, тогда как неизменные итераторы и коллекции могут.)

Кастинг с .Cast<T>()

Как уже упоминалось, .Cast<T>()можно применить к коллекции, чтобы спроектировать новую коллекцию элементов, приведенных к T, однако при этом произойдет бросок, InvalidCastExceptionесли приведение к одному или нескольким элементам невозможно (что будет таким же поведением, как при выполнении явного в ролях ОП foreach).

Фильтрация и кастинг с OfType<T>()

Если входной список содержит элементы разных несовместимых типов, потенциал InvalidCastExceptionможно избежать, используя .OfType<T>()вместо .Cast<T>(). ( .OfType<>()проверяет, можно ли преобразовать элемент в целевой тип перед попыткой преобразования, и отфильтровывает несовместимые типы.)

для каждого

Также обратите внимание, что если OP написал это вместо: (обратите внимание на явноеY y в foreach)

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

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

что кастинг также будет предпринят. Однако, если невозможно использовать приведение, результат InvalidCastExceptionбудет.

Примеры

Например, учитывая простую (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) { }
}

При работе с коллекцией смешанных типов:

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

В то время как:

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

отфильтровывает только слонов - т.е. зебры уничтожаются

Re: неявные операторы приведения

Без динамических определяемые пользователем операторы преобразования используются только во время компиляции *, поэтому, даже если оператор преобразования между скажем, Zebra и Elephant был сделан доступным, описанное выше поведение подходов к преобразованию во время выполнения не изменилось бы.

Если мы добавим оператор преобразования для преобразования зебры в слона:

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

Вместо этого, учитывая приведенный выше оператор преобразования, компилятор сможет изменить тип указанного ниже массива с Animal[]на Elephant[], учитывая, что теперь зебры можно преобразовать в однородную коллекцию слонов:

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

Использование операторов неявного преобразования во время выполнения

* Как отметил Эрик, оператор преобразования может быть доступен во время выполнения, прибегая к 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
источник
Эй, я только что попробовал пример «Использование foreach () для фильтрации типов», используя: var list = new List <object> () {1, "a", 2, "b", 3, "c", 4, " d "}; foreach (int i в списке) Console.WriteLine (i); и когда я запускаю его, я получаю «Указанное приведение неверно». Я что-то упускаю? Я не думал, что foreach работает таким образом, поэтому я пытался это сделать.
Брент Риттенхаус
Кроме того, это не ссылка на тип значения. Я только что попробовал это с базовым классом 'Thing' и двумя производными классами: 'Person' и 'Animal'. Когда я делаю то же самое с этим, я получаю: «Невозможно привести объект типа« Животное »к типу« Персона ».» Так что это определенно повторяется через каждый элемент. Если бы я должен был сделать OfType в списке, то он бы работал. ForEach, вероятно, будет очень медленным, если будет проверять это, если компилятор не оптимизирует его.
Брент Риттенхаус
Спасибо Брент - я был не в курсе. foreachне фильтрует, но использование более производного типа в качестве переменной итерации приведет к тому, что компилятор попытается выполнить приведение типа, что приведет к сбою на первом элементе, который не соответствует.
StuartLC
7

Ты можешь использовать List<Y>.ConvertAll<T>([Converter from Y to T]);

Андрей
источник
3

Это не совсем ответ на этот вопрос, но он может быть полезен для некоторых: как сказал @SWeko, благодаря ковариации и контравариантности List<X>не может быть брошен List<Y>, но List<X>может быть брошен IEnumerable<Y>, и даже неявным приведением.

Пример:

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

но

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

Большим преимуществом является то, что он не создает новый список в памяти.

Xav987
источник
1
Мне это нравится, потому что если у вас большой список исходных текстов, в начале не будет никакого снижения производительности. Вместо этого для каждой записи, обрабатываемой получателем, есть небольшой не заметный бросок. Также нет огромного накопления памяти. идеально подходит для обработки потоков.
Йохан
-2
dynamic data = List<x> val;  
List<y> val2 = ((IEnumerable)data).Cast<y>().ToList();
user2361134
источник