Коротший синтаксис для трансляції зі списку <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 )
  • Цей метод не працює для об'єкта, який має явний метод оператора (фреймворк 4.0)

12
Майте ще один золотий знак. Це було досить корисно.
ouflak

6
Потрібно включити наступний рядок, щоб компілятор розпізнав ці методи розширення: використання System.Linq;
гіпегуман

8
Також пам’ятайте, що хоч це відмічає кожен елемент у списку, сам список не подається; швидше створюється новий список із потрібним типом.
гіпегуман

4
Також пам’ятайте, що Cast<T>метод не підтримує власні оператори перетворення. Чому помічник ролі Linq не працює з оператором неявного ролі .
clD

Він не працює для об'єкта, який має явний метод оператора (фреймворк 4.0)
Адріан

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>класу, який може використовувати наданий вираз для перетворення елементів. Він має додане благо, що воно повертає a 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

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>є інваріантом типу Т і , отже , не має значення , є чи 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<>()перевіряє, чи можна елемент перетворити на цільовий тип перед спробою перетворення та відфільтровує незмінні типи.)

для кожного

Також зауважте, що якщо ОП написала це замість: (зверніть увагу на явне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

Привіт, я просто спробував приклад "Використання foreach () для фільтрації типів", використовуючи: var list = new List <object> () {1, "a", 2, "b", 3, "c", 4, " d "}; foreach (int i в списку) Console.WriteLine (i); і коли я запускаю його, я отримую "Вказаний склад недійсний". Я щось пропускаю? Я не думав, що foreach працював так, саме тому я намагався.
Brent Rittenhouse

Крім того, це не референтне та значення значення. Я просто спробував це з базовим класом "Річ" та двома похідними класами: "Особа" та "Тварина". Коли я роблю те саме, з цим я отримую: "Неможливо передати об’єкт типу" тварина ", щоб ввести" Особа "." Тож це безумовно повторюється через кожен елемент. Якби я мав робити OfType у списку, то це спрацювало б. ForEach, ймовірно, був би дуже повільним, якби це довелося перевірити, якщо тільки компілятор не оптимізував це.
Brent Rittenhouse

Спасибі Брент - я там був поза курсом. foreachне фільтрує, але використання більш похідного типу в якості змінної ітерації змусить компілятора здійснити спробу Cast, що не вдасться до першого елемента, який не відповідає.
StuartLC


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

Великою перевагою є те, що він не створює новий список в пам'яті.


1
Мені це подобається, тому що якщо у вас є великий список джерел, на початку не буде досягнуто результативності. Натомість є невеликий непомітний ролик для кожного запису, який обробляється одержувачем. Також не накопичується величезна пам'ять. ідеально підходить для обробки потоків.
Йохан Францен

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.