Более короткий синтаксис для приведения из списка в список?
Я знаю, что можно привести список элементов из одного типа в другой (учитывая, что ваш объект имеет открытый статический явный метод оператора для выполнения приведения) по одному за раз следующим образом:
List<Y> ListOfY = new List<Y>();
foreach(X x in ListOfX)
ListOfY.Add((Y)x);
но разве нельзя бросить весь список за один раз? Например,
ListOfY = (List<Y>)ListOfX;
4 ответа:
если
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)
прямой бросок
var ListOfY = (List<Y>)ListOfX
невозможно, потому что это потребует co / contravariance на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-это:
причина, почему бросок
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
здесь нет дополнительных модификаторов дисперсии)однако, если изменяемые коллекции не требуется в вашем дизайне, upcast на многих из неизменяемых коллекций,можно, например, при условии, что
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
если бросок на одном или больше элементов невозможно (что было бы тем же поведением, что и выполнение явного приведения в OPforeach
петли).фильтрация и литье с
OfType<T>()
если входной список содержит элементы разных, несовместимых типов, то потенциал
InvalidCastException
можно избежать с помощью.OfType<T>()
вместо.Cast<T>()
. (.OfType<>()
проверяет, может ли элемент быть преобразован в целевой тип, перед попыткой преобразования, и отфильтровывает несовместимые типы.)foreach
также обратите внимание, что если бы 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: неявные операторы приведения
без динамических пользовательских операторов преобразования используются только в времени компиляции*, поэтому даже если оператор преобразования между say 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