<X> Listesinden <Y> Listesine yayın yapmak için daha kısa sözdizimi?


237

Bir öğe diğerine (nesneniz döküm yapmak için bir genel statik açık operatör yöntemi olduğu göz önüne alındığında) birer birer aşağıdaki gibi döküm mümkün olduğunu biliyorum:

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

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

Ancak tüm listeyi bir defada yayınlamak mümkün değil mi? Örneğin,

ListOfY = (List<Y>)ListOfX;

@Oded: Bunu biraz daha netleştirmeye çalıştım. Endişelenme, sen anlamıyorsun, anlıyorum :)
BoltClock

1
X'in Y'den, Z'nin Y'den kaynaklandığını varsayarsak, Listenize <Y> gerçekten bir Liste <X> olan Z'yi eklerseniz ne olacağını düşünün.
Richard Friend

Yanıtlar:


497

Eğer Xgerçekten Ysize dökülebilirse kullanabilmelisiniz

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

Dikkat edilmesi gereken bazı şeyler (yorumculara H / T!)


12
Bir altın rozet daha al. Bu oldukça faydalı oldu.
ouflak

6
Derleyicinin bu uzantı yöntemlerini tanımasını sağlamak için aşağıdaki satırı içermelidir: using System.Linq;
hypehuman

8
Ayrıca, bu listedeki her öğeyi yayınlasa da listenin kendisinin yayınlanmadığını unutmayın; bunun yerine, istenen türde yeni bir liste oluşturulur.
hypehuman

4
Ayrıca, Cast<T>yöntemin özel dönüşüm operatörlerini desteklemediğini unutmayın. Linq Döküm Yardımcısı Neden Örtülü Döküm İşleci ile Çalışmıyor ?
clD

Açık bir işleç yöntemi olan bir nesne için çalışmaz (çerçeve 4.0)
Adrian

100

Doğrudan dökme var ListOfY = (List<Y>)ListOfXo gerektirecektir, çünkü mümkün değildir eş / contravariance ait List<T>tip, ve bu sadece her durumda garanti edilemez. Bu döküm sorununun çözümlerini görmek için lütfen okumaya devam edin.

Böyle bir kod yazabilmek normal gibi görünse de:

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

çünkü her memelinin bir hayvan olacağını garanti edebiliriz, bu kesinlikle bir hatadır:

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

çünkü her hayvan bir memeli değildir.

Ancak, C # 3 ve üzerini kullanarak

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

bu da dökümü biraz kolaylaştırıyor. Bu, Mammallistedeki her birini a'ya yayınlamak için açık bir yayın kullandığından Animalve yayın başarılı olmazsa başarısız olacağından tek tek ekleme kodunuzla sözdizimsel olarak eşdeğerdir .

Döküm / dönüştürme işlemi üzerinde daha fazla denetim isterseniz , öğeleri dönüştürmek için sağlanan bir ifadeyi kullanabilen sınıf ConvertAllyöntemini List<T>kullanabilirsiniz. Eklenmiş bir faydası vardır List, bunun yerine a'ya ihtiyaç IEnumerableduymaz .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
Şimdiye kadar bu cevabı hiç + 1'lediğime inanamıyorum. Yukarıdaki benimkinden çok daha iyi.
Jamiec

6
@Jamiec +1 yapmadım çünkü "Hayır, bu mümkün değil" ile başlıyor, bu soruyu bulan birçok kişinin aradığı cevabı gömerken. Teknik olarak, OP'nin sorusuna daha ayrıntılı bir şekilde cevap verdi.
Dan Bechard

13

Sweko'nun noktasına eklemek için:

Oyuncunun neden olmasının nedeni

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

mümkün değildir çünkü List<T>olan Tip T değişmez ve böylece olmadığını önemli değil Xkaynaklanmaktadır Y) - bunun nedeni olduğu List<T>gibi tanımlanmıştır:

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

(Bu bildirimde, Tburaya yazmanın ek varyans değiştiricileri olmadığını unutmayın)

Değişken koleksiyonları tasarım, değişmez koleksiyonlarının birçoğunda yukarı çevrim içinde gerekli değilse Ancak, mümkün örneğin şartıyla Giraffedan türemiştir Animal:

IEnumerable<Animal> animals = giraffes;

Bunun nedeni IEnumerable<T>kovaryansın desteklenmesidir T- bu IEnumerable, koleksiyona öğe ekleme veya toplama yöntemleri için desteği olmadığı için koleksiyonun değiştirilemeyeceği anlamına gelir. outAnahtar kelimeyi beyanında not edin IEnumerable<T>:

public interface IEnumerable<out T> : IEnumerable

( Değişmez yineleyiciler ve koleksiyonlar neden desteklenebilir gibi değişmez koleksiyonların neden desteklenemediğine dair daha fazla açıklama .)Listcovariance

İle yayınlanıyor .Cast<T>()

Diğerlerinin de belirttiği gibi, .Cast<T>()T'ye InvalidCastExceptiondökülen yeni bir eleman koleksiyonunu yansıtmak için bir koleksiyona uygulanabilir, ancak bunu yapmak bir veya daha fazla elemanın dökümü mümkün değilse (açık olanla aynı davranış olacaktır) OP foreachdöngüsünde döküm ).

Filtreleme ve Döküm OfType<T>()

Giriş listesi farklı, uyumsuz türden öğeler içeriyorsa, bunun yerine InvalidCastExceptionkullanılarak potansiyelden kaçınılabilir . ( dönüştürmeyi denemeden önce bir öğenin hedef türe dönüştürülüp dönüştürülemeyeceğini kontrol eder ve uyumsuz türleri filtreler.).OfType<T>().Cast<T>().OfType<>()

her biri için

(Not: Ayrıca OP yerine bu yazılı olsaydı unutmayın açıkY y içinde foreach)

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

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

döküm de denenecek. Ancak, oyuncu seçimi mümkün değilse, bir InvalidCastExceptionirade ortaya çıkar.

Örnekler

Örneğin, basit (C # 6) sınıf hiyerarşisi göz önüne alındığında:

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) { }
}

Karışık tiplerden oluşan bir koleksiyonla çalışırken:

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

Buna karşılık:

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

sadece Filleri filtreler - yani Zebralar elenir.

Re: Örtülü döküm operatörleri

Dinamik olmadan, kullanıcı tanımlı dönüşüm işleçleri yalnızca derleme zamanında * kullanılır, bu nedenle Zebra ve Fil arasında bir dönüştürme operatörü kullanıma sunulmuş olsa bile, dönüşüm yaklaşımlarının yukarıdaki çalışma süresi davranışı değişmez.

Zebra'yı Fil'e dönüştürmek için bir dönüşüm operatörü eklersek:

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

Bunun yerine, yukarıda dönüşüm operatör verilen derleyici dizisi aşağıdaki türünü değiştirmek mümkün olacaktır Animal[]için Elephant[]Zebralar hemen Filler homojen bir toplama dönüştürülebilir verilen:

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

Örtük Dönüştürme İşleçlerini çalışma zamanında kullanma

* Eric tarafından belirtildiği gibi, dönüşüm operatörüne aşağıdaki koşullarda başvurarak çalışma zamanında erişilebilir 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

Hey, ben sadece "tür filtreleme için foreach () kullanma" örneğini kullanarak denedim: var list = new List <object> () {1, "a", 2, "b", 3, "c", 4, " d "}; foreach (listedeki int i) Console.WriteLine (i); ve çalıştırdığımda "Belirtilen kadro geçerli değil." Bir şey mi kaçırıyorum? Foreach'in bu şekilde çalıştığını düşünmedim, bu yüzden deniyordum.
Brent Rittenhouse

Ayrıca, bir referans ve değer türü şey değil. Sadece 'Şey' temel sınıfıyla ve iki tür sınıfla denedim: 'Kişi' ve 'Hayvan'. Onunla aynı şeyi yaptığımda şunu elde ederim: "'Kişi' yazmak için 'Hayvan' türünde nesne kullanılamıyor." Yani kesinlikle her bir element için tekrar ediyor. Listede bir OfType yapmak olsaydı o zaman işe yarayacaktı. ForEach, derleyici optimize etmediği sürece, bunu kontrol etmek zorunda kalırsa muhtemelen çok yavaş olurdu.
Brent Rittenhouse

Teşekkürler Brent - Ben orada ders dışı idi. foreachfiltreleme yapmaz, ancak yineleme değişkeni olarak daha türetilmiş bir tür kullanıldığında, derleyici, uygun olmayan ilk öğede başarısız olacak bir Cast girişimine zorlanır.
StuartLC

7

Kullanabilirsiniz List<Y>.ConvertAll<T>([Converter from Y to T]);


3

Bu, bu soruya oldukça cevap değil, ama bazıları için yararlı olabilir: @SWeko söylediği gibi, kovaryans ve contravariance sayesinde List<X>dökme edilemez List<Y>, fakat List<X>içine dökülebilirIEnumerable<Y> ve hatta örtük bir cast ile.

Misal:

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

fakat

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

En büyük avantajı, bellekte yeni bir liste oluşturmamasıdır.


1
Bunu beğendim çünkü büyük bir kaynak listeniz varsa, başlangıçta performans isabeti yoktur. Bunun yerine, alıcı tarafından işlenen her bir giriş için fark edilmeyen küçük bir döküm vardır. Ayrıca büyük bir bellek birikmesi yok. akışları işlemek için mükemmeldir.
Johan Franzén

-2
dynamic data = List<x> val;  
List<y> val2 = ((IEnumerable)data).Cast<y>().ToList();
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.