<DerivedClass> Listesini <BaseClass> Listesine Dönüştür


Yanıtlar:


230

Bu işi yapmanın yolu, listeyi tekrarlamak ve öğeleri yayınlamaktır. Bu, ConvertAll kullanılarak yapılabilir:

List<A> listOfA = new List<C>().ConvertAll(x => (A)x);

Linq'i de kullanabilirsiniz:

List<A> listOfA = new List<C>().Cast<A>().ToList();

2
başka bir seçenek: List <A> listOfA = listOfC.ConvertAll (x => (A) x);
ahaliav fox

6
Hangisi daha hızlı? ConvertAllveya Cast?
Martin Braun

24
Bu listenin bir kopyasını oluşturur. Yeni listeye bir şey ekler veya kaldırırsanız, bu orijinal listeye yansıtılmaz. İkincisi, mevcut nesnelerle yeni bir liste oluşturduğundan büyük bir performans ve hafıza cezası var. Bu sorunları olmayan bir çözüm için aşağıdaki cevabıma bakın.
Bigjim

ModiX cevabı: ConvertAll Liste <T> 'de bir yöntemdir, bu yüzden sadece genel bir listede çalışacaktır; IEnumerable veya IEnumerable <T> üzerinde çalışmaz; ConvertAll ayrıca yalnızca döküm değil, örneğin ConvertAll (inç => inç * 25,4) gibi özel dönüşümler de yapabilir. Cast <A> bir LINQ genişletme yöntemidir, bu nedenle herhangi bir IEnumerable <T> üzerinde çalışır (ve ayrıca genel olmayan IEnumerable için de çalışır) ve LINQ'nun çoğunda olduğu gibi, ertelenmiş yürütme kullanır, yani yalnızca birçok öğeyi dönüştürür alınan. Burada daha fazla bilgi bulabilirsiniz: codeblog.jonskeet.uk/2011/01/13/…
Edward

172

Her şeyden önce, A, B, C gibi anlaşılması imkansız sınıf isimlerini kullanmayı bırakın. Hayvan, Memeli, Zürafa veya Yiyecek, Meyve, Portakal veya ilişkilerin açık olduğu bir şey kullanın.

O zaman sorunuz "hayvan türü değişkenine neden bir zürafa listesi atamıyorum, çünkü hayvan türü değişkenine bir zürafa atayabiliyorum?"

Cevap: varsayalım. O zaman ne ters gidebilir ki?

Hayvan listesine bir Tiger ekleyebilirsiniz. Hayvanların listesini tutan bir değişkene zürafalar listesi koymanıza izin verdiğimizi varsayalım. Sonra bu listeye bir kaplan eklemeye çalışın. Ne oluyor? Zürafalar listesinin bir kaplan içermesini ister misiniz? Bir çarpışma ister misiniz? ya da derleyicinin görevi ilk etapta yasadışı yaparak sizi kazadan korumasını mı istiyorsunuz?

İkincisini seçiyoruz.

Bu tür dönüşümlere "kovaryant" dönüşüm adı verilir. C # 4'te , dönüşümün her zaman güvenli olduğu bilindiğinde arayüzler ve delegeler üzerinde kovaryant dönüşümler yapmanıza izin vereceğiz . Ayrıntılar için kovaryans ve kontravaryans hakkında blog makalelerime bakın. (Bu hafta, bu haftanın Pazartesi ve Perşembe günleri yeni bir konu olacak.)


3
Bir IList <T> veya ICollection <T> jenerik olmayan IList uygulamakla ilgili, ancak jenerik olmayan Ilist / ICollection uygulaması IsReadOnly için True döndürerek uygulamak ve onu değiştirecek herhangi bir yöntem veya özellik için NotSupportedException atar mıydı?
supercat

33
Bu cevap oldukça kabul edilebilir bir muhakeme içermekle birlikte, gerçekten "doğru" değildir. Basit cevap, C #'ın bunu desteklememesidir. Zürafalar ve kaplanlar içeren hayvanların bir listesine sahip olma fikri tamamen geçerlidir. Tek sorun, daha üst düzey sınıflara erişmek istediğinizde gelir. Gerçekte bu, bir üst sınıfı bir işleve parametre olarak geçirmekten ve daha sonra farklı bir kardeş sınıfına aktarmaya çalışmaktan farklı değildir. Oyuncunun uygulanmasıyla ilgili teknik sorunlar olabilir, ancak yukarıdaki açıklama bunun kötü bir fikir olması için herhangi bir neden sunmamaktadır.
Paul Coldrey

2
@EricLippert Neden bu dönüşümü IEnumerablea yerine kullanalım List? yani: List<Animal> listAnimals = listGiraffes as List<Animal>;mümkün değil, IEnumerable<Animal> eAnimals = listGiraffes as IEnumerable<Animal>çalışıyor.
LINQ

3
@jbueno: Cevabımın son paragrafını oku. Buradaki dönüşümün güvenli olduğu bilinmektedir . Neden? Çünkü bir zürafa dizisini bir hayvan dizisine dönüştürmek ve daha sonra hayvanların dizisine bir kaplan koymak mümkün değildir . IEnumerable<T>ve IEnumerator<T>her ikisi de kovaryans için güvenli olarak işaretlenmiştir ve derleyici bunu doğrulamıştır.
Eric Lippert

60

Eric'in büyük açıklamasını alıntılamak için

Ne oluyor? Zürafalar listesinin bir kaplan içermesini ister misiniz? Bir çarpışma ister misiniz? ya da derleyicinin ödevi yasadışı hale getirerek sizi kazadan korumasını mı istiyorsunuz? İkincisini seçiyoruz.

Ancak derleme hatası yerine bir çalışma zamanı çökmesi seçmek isterseniz ne olur? Normalde Cast <> veya ConvertAll <> kullanırsınız, ancak 2 sorununuz olur: Listenin bir kopyasını oluşturur. Yeni listeye bir şey ekler veya kaldırırsanız, bu orijinal listeye yansıtılmaz. İkincisi, mevcut nesnelerle yeni bir liste oluşturduğundan büyük bir performans ve hafıza cezası var.

Aynı sorunu yaşadım ve bu nedenle tamamen yeni bir liste oluşturmadan genel bir liste oluşturabilen bir sarmalayıcı sınıfı oluşturdum.

Orijinal soruda şunları kullanabilirsiniz:

class Test
{
    static void Main(string[] args)
    {
        A a = new C(); // OK
        IList<A> listOfA = new List<C>().CastList<C,A>(); // now ok!
    }
}

ve burada sarıcı sınıfı (+ kolay kullanım için bir genişletme yöntemi CastList)

public class CastedList<TTo, TFrom> : IList<TTo>
{
    public IList<TFrom> BaseList;

    public CastedList(IList<TFrom> baseList)
    {
        BaseList = baseList;
    }

    // IEnumerable
    IEnumerator IEnumerable.GetEnumerator() { return BaseList.GetEnumerator(); }

    // IEnumerable<>
    public IEnumerator<TTo> GetEnumerator() { return new CastedEnumerator<TTo, TFrom>(BaseList.GetEnumerator()); }

    // ICollection
    public int Count { get { return BaseList.Count; } }
    public bool IsReadOnly { get { return BaseList.IsReadOnly; } }
    public void Add(TTo item) { BaseList.Add((TFrom)(object)item); }
    public void Clear() { BaseList.Clear(); }
    public bool Contains(TTo item) { return BaseList.Contains((TFrom)(object)item); }
    public void CopyTo(TTo[] array, int arrayIndex) { BaseList.CopyTo((TFrom[])(object)array, arrayIndex); }
    public bool Remove(TTo item) { return BaseList.Remove((TFrom)(object)item); }

    // IList
    public TTo this[int index]
    {
        get { return (TTo)(object)BaseList[index]; }
        set { BaseList[index] = (TFrom)(object)value; }
    }

    public int IndexOf(TTo item) { return BaseList.IndexOf((TFrom)(object)item); }
    public void Insert(int index, TTo item) { BaseList.Insert(index, (TFrom)(object)item); }
    public void RemoveAt(int index) { BaseList.RemoveAt(index); }
}

public class CastedEnumerator<TTo, TFrom> : IEnumerator<TTo>
{
    public IEnumerator<TFrom> BaseEnumerator;

    public CastedEnumerator(IEnumerator<TFrom> baseEnumerator)
    {
        BaseEnumerator = baseEnumerator;
    }

    // IDisposable
    public void Dispose() { BaseEnumerator.Dispose(); }

    // IEnumerator
    object IEnumerator.Current { get { return BaseEnumerator.Current; } }
    public bool MoveNext() { return BaseEnumerator.MoveNext(); }
    public void Reset() { BaseEnumerator.Reset(); }

    // IEnumerator<>
    public TTo Current { get { return (TTo)(object)BaseEnumerator.Current; } }
}

public static class ListExtensions
{
    public static IList<TTo> CastList<TFrom, TTo>(this IList<TFrom> list)
    {
        return new CastedList<TTo, TFrom>(list);
    }
}

1
Ben sadece MVC görünüm modeli olarak kullandım ve güzel evrensel jilet kısmi görünümü var. Harika fikir! Bunu okuduğum için çok mutluyum.
Stefan Cebulak

5
Sınıf bildiriminde yalnızca "where TTo: TFrom" ekleyeceğim, böylece derleyici yanlış kullanıma karşı uyarı verebilir. İlişkisiz türlerden bir CastedList yapmak zaten saçmadır ve "CastedList <TBase, TDerived>" yapmak işe yaramaz olacaktır: Buna düzenli TBase nesneleri ekleyemezsiniz ve orijinal Listeden aldığınız herhangi bir TDeri zaten olarak kullanılabilir bir TBaz.
Kurtzoon

2
@PaulColdrey Alas, altı yıllık bir başlangıcı suçlamak.
Wolfzoon

@Wolfzoon: Standart .Cast <T> () de bu kısıtlamaya sahip değil. Davranışı .Cast ile aynı hale getirmek istedim, bu yüzden Hayvanlar listesini Kaplanlar listesine dökmek ve örneğin bir Zürafa içerdiğinde bir istisna yapmak mümkün. Tıpkı Cast ile olduğu gibi ...
Bigjim

Merhaba @Bigjim, mevcut bir Zürafalar dizisini Hayvanlar (temel sınıf) bir dizi içine sarmak için sarıcı sınıf nasıl kullanılacağını anlamakta sorun yaşıyorum. Herhangi bir ipucu appriciated olacak :)
Denis Vitez

28

Eğer kullanırsanız IEnumerablebunun yerine, (C # 4.0 azından önceki sürümleri denemedim) çalışacaktır. Bu sadece bir oyuncu kadrosu, tabii ki hala bir liste olacak.

Onun yerine -

List<A> listOfA = new List<C>(); // compiler Error

Sorunun orijinal kodunda -

IEnumerable<A> listOfA = new List<C>(); // compiler error - no more! :)


Bu durumda IEnumerable nasıl kullanılır?
Vladius

1
List<A> listOfA = new List<C>(); // compiler ErrorSorunun orijinal kodu yerine , girinIEnumerable<A> listOfA = new List<C>(); // compiler error - no more! :)
PhistucK

IEnumerable <BaseClass> parametresini parametre olarak alan yöntem, devralınan sınıfın bir Liste olarak iletilmesine izin verecektir. Parametre tipini değiştirmek dışında yapacak çok az şey var.
beauXjames

27

Neden işe yaramadığı kadarıyla anlamak, kovaryans ve karşıtlığı .

Bunun neden işe yaramaması gerektiğini göstermek için, sağladığınız kodda bir değişiklik:

void DoesThisWork()
{
     List<C> DerivedList = new List<C>();
     List<A> BaseList = DerivedList;
     BaseList.Add(new B());

     C FirstItem = DerivedList.First();
}

Bu işe yaramalı mı? Listedeki ilk öğe "B" türünde, ancak DerivedList öğesinin türü C'dir.

Şimdi, gerçekten A'yı uygulayan bir tür listede çalışan genel bir işlev yapmak istediğimizi varsayalım, ancak bunun ne tür olduğu umurumda değil:

void ThisWorks<T>(List<T> GenericList) where T:A
{

}

void Test()
{
     ThisWorks(new List<B>());
     ThisWorks(new List<C>());
}

"Bu işe yarayacak mı?" - Evet ve hayır. Neden 'C' türü olarak FirstItem eriştiğinizde aslında geçersiz bir dönüşüm yapıyor çünkü kodu yazmak ve derleme zamanında başarısız olmamalıdır hiçbir neden görmüyorum. Desteklenen C # 'ı havaya uçurmak için birçok benzer yol vardır. EĞER sonra Büyük Jim en aşağıda cevap aslında iyi bir neden için bu işlevselliği elde etmek istiyorsanız (ve çok var) harika.
Paul Coldrey

16

Yalnızca salt okunur listelere yayınlayabilirsiniz. Örneğin:

IEnumerable<A> enumOfA = new List<C>();//This works
IReadOnlyCollection<A> ro_colOfA = new List<C>();//This works
IReadOnlyList<A> ro_listOfA = new List<C>();//This works

Kaydetme öğelerini destekleyen listeler için bunu yapamazsınız. Nedeni:

List<string> listString=new List<string>();
List<object> listObject=(List<object>)listString;//Assume that this is possible
listObject.Add(new object());

Şimdi ne var? ListObject ve listString öğelerinin aslında aynı liste olduğunu unutmayın, bu nedenle listString öğesinde artık nesne öğesi vardır - bu mümkün olmamalı ve olmamalıdır.


1
Bu benim için en anlaşılır cevaptı. Özellikle IReadOnlyList adında bir şey olduğundan bahseder, bu da işe yarayacağından daha fazla öğe eklenmeyeceğini garanti eder.
bendtherules

IReadOnlyDictionary <TKey, TValue> için benzer bir şey yapamazsınız. Neden?
Alastair Maw

Bu harika bir öneri. Kolaylık sağlamak için her yerde Liste <> kullanıyorum, ancak çoğu zaman salt okunur olmasını istiyorum. Bu öneri bu döküm izin vererek ve salt okunur belirtmek nerede geliştirerek kodumu 2 şekilde artıracak gibi güzel öneri.
Rick Love

1

Ben şahsen derslerin uzantıları ile kütüphaneler oluşturmak istiyorum

public static List<TTo> Cast<TFrom, TTo>(List<TFrom> fromlist)
  where TFrom : class 
  where TTo : class
{
  return fromlist.ConvertAll(x => x as TTo);
}

0

Çünkü C # bu tür mirasŞu anda dönüşüm .


6
Birincisi, bu bir kalıtım sorunu, kalıtım değil. İkincisi, genel türlerin kovaryansı sınıf türlerinde çalışmaz, yalnızca arabirim ve temsilci türlerinde çalışır.
Eric Lippert

5
Seninle pek tartışamıyorum.
Öğlen İpek

0

Bu, BigJim'in parlak cevabının bir uzantısıdır .

Benim durumumda sözlüğe sahip bir NodeBasesınıfım vardı Childrenve çocuklardan genel olarak O (1) aramaları yapmanın bir yoluna ihtiyacım vardı. Getter'de özel bir sözlük alanı döndürmeye çalışıyordum Children, bu yüzden açıkçası pahalı kopyalama / yineleme yapmak istemedim. Bu nedenle Dictionary<whatever specific type>genel bir oyuncuya Bigjim'in kodunu kullandım Dictionary<NodeBase>:

// Abstract parent class
public abstract class NodeBase
{
    public abstract IDictionary<string, NodeBase> Children { get; }
    ...
}

// Implementing child class
public class RealNode : NodeBase
{
    private Dictionary<string, RealNode> containedNodes;

    public override IDictionary<string, NodeBase> Children
    {
        // Using a modification of Bigjim's code to cast the Dictionary:
        return new IDictionary<string, NodeBase>().CastDictionary<string, RealNode, NodeBase>();
    }
    ...
}

Bu iyi çalıştı. Ancak, sonunda ilgisiz sınırlamalarla karşılaştım ve FindChild()temel sınıfta bunun yerine aramaları yapacak soyut bir yöntem yarattım. Anlaşıldığı gibi bu, ilk başta döküm sözlüğü ihtiyacını ortadan kaldırdı. ( IEnumerableAmaçlarım için bir basit ile değiştirebildim .)

Bu nedenle sorabileceğiniz soru (özellikle performans, .Cast<>veya ' yı kullanmanızı yasaklayan bir sorunsa .ConvertAll<>):

"Gerçekten tüm koleksiyonu kullanmam gerekiyor mu, yoksa görevi gerçekleştirmek için gereken özel bilgileri tutmak ve böylece koleksiyona doğrudan erişmekten kaçınmak için soyut bir yöntem kullanabilir miyim?"

Bazen en basit çözüm en iyisidir.


0

System.Runtime.CompilerServices.UnsafeNuGet paketini, aynı referansa referans oluşturmak için de kullanabilirsiniz List:

using System.Runtime.CompilerServices;
...
class Tool { }
class Hammer : Tool { }
...
var hammers = new List<Hammer>();
...
var tools = Unsafe.As<List<Tool>>(hammers);

Yukarıdaki örnek verildiğinde, değişkeni Hammerkullanarak listedeki mevcut örneklere erişebilirsiniz tools. ToolListeye örnekler eklemek bir ArrayTypeMismatchExceptionistisna atar, çünkü toolsaynı değişkene başvurur hammers.


0

Bu konuyu okudum ve sadece bana bir tutarsızlık gibi görünen şeyleri belirtmek istiyorum.

Derleyici, Listeler ile ödev yapmanızı engeller:

List<Tiger> myTigersList = new List<Tiger>() { new Tiger(), new Tiger(), new Tiger() };
List<Animal> myAnimalsList = myTigersList;    // Compiler error

Ancak derleyici dizilerle mükemmel durumda:

Tiger[] myTigersArray = new Tiger[3] { new Tiger(), new Tiger(), new Tiger() };
Animal[] myAnimalsArray = myTigersArray;    // No problem

Görevin güvenli olup olmadığı hakkında tartışma burada ayrılıyor. Dizi ile yaptığım ödev güvenli değil . Bunu kanıtlamak için, bunu takip edersem:

myAnimalsArray[1] = new Giraffe();

Bir çalışma zamanı özel durumu "ArrayTypeMismatchException" olsun. Kişi bunu nasıl açıklar? Derleyici gerçekten aptalca bir şey yapmama engel olmak istiyorsa, dizi atamasını yapmama engel olmalıydı.

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.