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


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

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

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

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

ListOfY = (List<Y>)ListOfX;
4 180

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 если бросок на одном или больше элементов невозможно (что было бы тем же поведением, что и выполнение явного приведения в OP foreach петли).

фильтрация и литье с 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

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