IEnumerable vs List - Ne Kullanılır? Nasıl çalışırlar?


678

Numaralandırıcıların ve LINQ'nun nasıl çalıştığı konusunda bazı şüphelerim var. Bu iki basit seçimi düşünün:

List<Animal> sel = (from animal in Animals 
                    join race in Species
                    on animal.SpeciesKey equals race.SpeciesKey
                    select animal).Distinct().ToList();

veya

IEnumerable<Animal> sel = (from animal in Animals 
                           join race in Species
                           on animal.SpeciesKey equals race.SpeciesKey
                           select animal).Distinct();

Orijinal nesnelerimin adlarını değiştirdim, böylece bu daha genel bir örnek gibi görünecek. Sorgunun kendisi o kadar önemli değil. Sormak istediğim şu:

foreach (Animal animal in sel) { /*do stuff*/ }
  1. IEnumerable"Sel" hata ayıklama ve inceleme, bu durumda IEnumerable olduğunu fark edersem , bazı ilginç üyeleri vardır: "iç", "dış", "innerKeySelector" ve "dışKeySelector", bu son 2 görünür delege olmak. "İç" üyenin içinde "Hayvan" örnekleri değil, benim için çok garip olan "Türler" örnekleri vardır. "Dış" üye "Hayvan" örnekleri içerir. Sanırım iki delege hangisinin içeri girip hangisinin dışarı çıktığını belirledi?

  2. Ben "Farklı" kullanırsanız, "iç" 6 öğe içerdiğini fark (sadece 2 farklı olduğu gibi bu yanlış), ama "dış" doğru değerleri içeriyor. Yine, muhtemelen temsil edilen yöntemler bunu belirler, ancak bu IEnumerable hakkında bildiğimden biraz daha fazladır.

  3. En önemlisi, iki seçenekten hangisi en iyi performans açısından?

Aracılığıyla şeytani Liste dönüşüm .ToList()?

Ya da doğrudan numaralandırıcıyı mı kullanıyorsunuz?

Yapabiliyorsanız, lütfen biraz açıklayın veya IEnumerable'ın bu kullanımını açıklayan bazı bağlantılar atın.

Yanıtlar:


740

IEnumerableDavranışı açıklarken Liste bu davranışın bir uygulamasıdır. Kullandığınızda IEnumerable, derleyiciye işi ileride erteleme şansı verirsiniz, muhtemelen yol boyunca optimizasyon yaparsınız. ToList () kullanırsanız, derleyiciyi sonuçları hemen anlamaya zorlarsınız.

LINQ ifadelerini "istiflediğimde" kullanıyorum IEnumerable, çünkü sadece davranışı belirleyerek LINQ'ya değerlendirmeyi erteleme ve muhtemelen programı optimize etme şansı veriyorum. LINQ, siz numaralandırıncaya kadar veritabanını sorgulamak için SQL'i nasıl oluşturmadığını hatırlıyor musunuz? Bunu düşün:

public IEnumerable<Animals> AllSpotted()
{
    return from a in Zoo.Animals
           where a.coat.HasSpots == true
           select a;
}

public IEnumerable<Animals> Feline(IEnumerable<Animals> sample)
{
    return from a in sample
           where a.race.Family == "Felidae"
           select a;
}

public IEnumerable<Animals> Canine(IEnumerable<Animals> sample)
{
    return from a in sample
           where a.race.Family == "Canidae"
           select a;
}

Artık ilk örneği ("AllSpotted") ve bazı filtreleri seçen bir yönteminiz var. Şimdi bunu yapabilirsiniz:

var Leopards = Feline(AllSpotted());
var Hyenas = Canine(AllSpotted());

List over kullanmak daha mı hızlı IEnumerable? Yalnızca bir sorgunun birden çok kez yürütülmesini önlemek istiyorsanız. Ama genel olarak daha mı iyi? Yukarıda, Leoparlar ve Sırtlanların her biri tek SQL sorgularına dönüştürülür ve veritabanı yalnızca ilgili satırları döndürür. Ancak bir List'i döndürdüysek AllSpotted(), veritabanı gerçekten gerekenden çok daha fazla veri döndürebileceğinden daha yavaş çalışabilir ve istemcide filtrelemeyi yapan döngüleri boşa harcarız.

Bir programda, sorgunuzu sonuna kadar bir listeye dönüştürmeyi ertelemek daha iyi olabilir, bu yüzden Leopards ve Hyenas üzerinden bir kereden fazla numaralandırırsam, bunu yaparım:

List<Animals> Leopards = Feline(AllSpotted()).ToList();
List<Animals> Hyenas = Canine(AllSpotted()).ToList();

11
Sanırım bir birleşmenin iki tarafına atıfta bulunuyorlar. Eğer "SELECT * FROM Animals Joins Specins ..." yaparsanız, birleştirmenin iç kısmı Animals ve dış kısmı Species olur.
Chris Wenham

10
Hakkında cevapları okuduğumda: IEnumerable <T> vs IQueryable <T> Ben analog açıklaması gördüm, böylece IEnumerable otomatik olarak çalışma zamanı koleksiyonu sorgulamak için nesneleri LINQ kullanmaya zorlar. Bu yüzden bu 3 tip arasında kafam karıştı. stackoverflow.com/questions/2876616/…
Bronek

4
@Bronek Bağladığınız cevap doğrudur. IEnumerable<T>ilk bölümden sonra LINQ-To-Objects olacak, yani tüm benekli Feline'i çalıştırmak için geri dönmek zorunda kalacak. Öte yandan IQuertable<T>, sadece Benekli Köpekler aşağı çekerek, sorgunun rafine edilmesine izin verecektir.
Nate

21
Bu cevap çok yanıltıcı! @ Nate'in yorumu nedenini açıklar. IEnumerable <T> kullanıyorsanız, filtre ne olursa olsun istemci tarafında gerçekleşir.
Hans

5
Evet AllSpotted () iki kez çalıştırılır. Bu yanıtla ilgili en büyük sorun şu ifadedir: "Yukarıda Leopards ve Hyenas her biri tek bir SQL sorgusuna dönüştürülür ve veritabanı yalnızca ilgili satırları döndürür." Bu yanlıştır, çünkü where cümlesi bir IEnumerable <> üzerinde çağrılır ve bu sadece veritabanından zaten gelen nesneler arasında nasıl döngü yapılacağını bilir. AllSpotted () ve Feline () ve Canine () parametrelerini IQueryable'a döndürdüyseniz, filtre SQL'de gerçekleşir ve bu cevap mantıklı olur.
Hans

178

Yazan çok iyi bir makale var: Claudio Bernasconi'nin TechBlog'u burada: IEnumerable, ICollection, IList ve List ne zaman kullanılır?

İşte bazı temel senaryolar ve fonksiyonlar:

resim açıklamasını buraya girin resim açıklamasını buraya girin


25
Bu makalenin, iç çalışmalara değil, kodunuzun yalnızca halka açık kısımlarına yönelik olduğuna dikkat edilmelidir. Listbir uygulamasıdır IListve bu tür olanların üstüne ekstra işlevselliğe sahip olarak IList(örneğin Sort, Find, InsertRange). Kullanmak kendinizi zorlarsanız IListüzerinde List, size gerektirebilir bu yöntemleri gevşek
Jonathan Twite

4
UnutmayınIReadOnlyCollection<T>
Dandré

2
[]Buraya düz bir dizi eklemek de yararlı olabilir .
jbyrd

Kaşlarını çatmış olsa da, bu grafiği ve makaleyi paylaştığınız için teşekkür ederiz
Daniel

134

Uygulayan bir sınıf sözdizimini IEnumerablekullanmanıza izin verir foreach.

Temel olarak koleksiyondaki bir sonraki öğeyi almak için bir yöntemi vardır. Tüm koleksiyonun bellekte olması gerekmez ve içinde kaç öğe olduğunu bilmez, foreachsadece bitene kadar bir sonraki öğeyi almaya devam eder.

Bu, bazı durumlarda çok yararlı olabilir, örneğin, büyük bir veritabanı tablosunda, satırları işlemeye başlamadan önce her şeyi belleğe kopyalamak istemezsiniz.

Şimdi Listuygular IEnumerable, ancak bellekteki tüm koleksiyonu temsil eder. Eğer varsa IEnumerableve çağırırsanız .ToList(), hafızadaki numaralandırmanın içeriğini içeren yeni bir liste oluşturursunuz.

Linq ifadeniz bir numaralandırma döndürür ve varsayılan olarak foreach,. Bir IEnumerablelinq deyimi yinelediğinizde yürütülür foreach, ancak daha erken kullanarak yinelemeye zorlayabilirsiniz .ToList().

Demek istediğim şu:

var things = 
    from item in BigDatabaseCall()
    where ....
    select item;

// this will iterate through the entire linq statement:
int count = things.Count();

// this will stop after iterating the first one, but will execute the linq again
bool hasAnyRecs = things.Any();

// this will execute the linq statement *again*
foreach( var thing in things ) ...

// this will copy the results to a list in memory
var list = things.ToList()

// this won't iterate through again, the list knows how many items are in it
int count2 = list.Count();

// this won't execute the linq statement - we have it copied to the list
foreach( var thing in list ) ...

2
Peki bir IEnumerable'da foreach öğesini önce Listeye dönüştürmeden yürütürseniz ne olur ? Tüm koleksiyonu hafızaya getiriyor mu? Veya, foreach döngüsü üzerinden yinelendiği için öğeyi tek tek mi başlatıyor? teşekkürler
Pap

@ İkincisini tutun: tekrar çalıştırılır, hiçbir şey otomatik olarak bellekte önbelleğe alınmaz.
Keith

anahtar dif 1 gibi görünüyor bellekte her şey ya da değil. 2) IEnumerable kullanmama izin foreachverirken List söz dizini ile gider. Ben bilmek istiyorum Şimdi, eğer sayım / uzunluğu arasında thingönceden, IEnumerable değil bize yardım eder, değil mi?
Jeb50

@ Jeb50 Tam olarak değil - her ikisi de Listve Arrayuygulayın IEnumerable. IEnumerableHem bellek koleksiyonlarında hem de her seferinde bir öğe alan büyük olanlar için çalışan en düşük ortak payda olarak düşünebilirsiniz . Aradığınızda IEnumerable.Count(), hızlı bir .Lengthmülkü çağırıyor veya tüm koleksiyonu geçiyor olabilirsiniz - mesele şu ki, IEnumerablebilmiyorsunuz. Bu bir sorun olabilir, ancak sadece buna gidiyorsanız foreachumursamıyorsunuz - kodunuz bir Arrayveya DataReaderaynı ile çalışacaktır .
Keith

1
@MFouadKajj Hangi yığını kullandığınızı bilmiyorum, ancak neredeyse her satırda bir istekte bulunmuyor. Sunucu sorguyu çalıştırır ve sonuç kümesinin başlangıç ​​noktasını hesaplar, ancak her şeyi almaz. Küçük sonuç kümeleri için bu tek bir yolculuk olabilir, büyük olanlar için sonuçlardan daha fazla satır isteği gönderirsiniz, ancak tüm sorguyu yeniden çalıştırmaz.
Keith

97

Kimse önemli bir farktan bahsetmedi, ironik bir şekilde bunun bir kopyası olarak kapatılan bir soruya cevap verdi.

IEnumerable salt okunurdur ve List değildir.

Bkz. Liste ve IEnumerable arasındaki pratik fark


Bir takip olarak, bu Arayüz yönü veya Liste yönü nedeniyle mi? IList de salt okunur mudur?
Jason Masters

IList salt okunur değildir - docs.microsoft.com/tr-tr/dotnet/api/… IEnumerable salt okunurdur, çünkü kurulduktan sonra herhangi bir şey eklemek veya kaldırmak için herhangi bir yöntemden yoksundur, temel arayüzlerden biridir. IList uzanıyor (linke bakınız)
CAD bloke

67

Gerçekleştirilmesi gereken en önemli şey, Linq kullanarak sorgunun hemen değerlendirilmemesidir. Sadece sonuçta IEnumerable<T>bir yineleme sürecinin bir parçası olarak çalıştırılır foreach- tüm garip delegelerin yaptığı şey budur.

Bu nedenle, ilk örnek ToList, sorgu sonuçlarını arayarak ve bir listeye koyarak sorguyu hemen değerlendirir .
İkinci örnek IEnumerable<T>, sorguyu daha sonra çalıştırmak için gereken tüm bilgileri içeren bir döndürür .

Performans açısından cevap buna bağlıdır . Sonuçların bir kerede değerlendirilmesine ihtiyacınız varsa (örneğin, daha sonra sorguladığınız yapıları mutasyona uğrattıysanız veya üzerinden yinelemenin IEnumerable<T>uzun sürmesini istemiyorsanız ) bir liste kullanın. Başka bir kullanım IEnumerable<T>. Varsayılan, ikinci listede isteğe bağlı değerlendirmeyi kullanmaktır, çünkü sonuçları bir listede saklamak için belirli bir neden olmadıkça, genellikle daha az bellek kullanır.


Merhaba ve cevap verdiğiniz için teşekkürler :: -). Bu neredeyse tüm şüphelerimi ortadan kaldırdı. Numaralandırılmasının neden "iç" ve "dış" olarak "bölünmüş" olduğuna dair bir fikrin var mı? Bu, öğeyi fare ile hata ayıklama / kesme modunda incelediğimde olur. Bu belki de Visual Studio'nun katkısı mı? Yerinde numaralandırma ve Enum'un giriş ve çıkışını gösterme?
Axonn

5
Bu Joinişi yapıyor - iç ve dış birleşimin iki yüzü. Genellikle, IEnumerablesgerçek kodunuzdan tamamen farklı olacağından, aslında ne olduğu konusunda endişelenmeyin . Gerçek çıktı hakkında sadece endişelendiğinizde endişelenin :)
thecoop

40

IEnumerable'un avantajı ertelenmiş yürütmedir (genellikle veritabanlarıyla). Siz gerçekten veriler arasında döngü kadar sorgu yürütülmez. Gerekli olana kadar bekleyen bir sorgu (tembel yükleme olarak da bilinir).

ToList'i çağırırsanız, sorgu yürütülür veya söylemek istediğim gibi "gerçekleşir".

Her ikisinin de artıları ve eksileri vardır. ToList'i çağırırsanız, sorgunun ne zaman yürütüldüğüne ilişkin gizemi kaldırabilirsiniz. IEnumerable'a bağlı kalırsanız, programın gerçekten gerekli olana kadar herhangi bir iş yapmaması avantajını elde edersiniz.


25

Bir güne düştüğüm yanlış bir kavramı paylaşacağım:

var names = new List<string> {"mercedes", "mazda", "bmw", "fiat", "ferrari"};

var startingWith_M = names.Where(x => x.StartsWith("m"));

var startingWith_F = names.Where(x => x.StartsWith("f"));


// updating existing list
names[0] = "ford";

// Guess what should be printed before continuing
print( startingWith_M.ToList() );
print( startingWith_F.ToList() );

Beklenen Sonuç

// I was expecting    
print( startingWith_M.ToList() ); // mercedes, mazda
print( startingWith_F.ToList() ); // fiat, ferrari

Gerçek sonuç

// what printed actualy   
print( startingWith_M.ToList() ); // mazda
print( startingWith_F.ToList() ); // ford, fiat, ferrari

açıklama

Diğer cevaplara göre, örneğin değerlendirilmesi çağrı ToListveya benzer çağırma yöntemlerine kadar ertelenmiştir ToArray.

Yani bu durumda kodu yeniden yazabilirsiniz:

var names = new List<string> {"mercedes", "mazda", "bmw", "fiat", "ferrari"};

// updating existing list
names[0] = "ford";

// before calling ToList directly
var startingWith_M = names.Where(x => x.StartsWith("m"));

var startingWith_F = names.Where(x => x.StartsWith("f"));

print( startingWith_M.ToList() );
print( startingWith_F.ToList() );

Oyun alanı

https://repl.it/E8Ki/0


1
Bunun nedeni, bu durumda IEnumerable'dan gelen ve yalnızca sorgu oluşturmayan ancak yürütmeyen linq yöntemleri (uzantı) nedeniyle (ifade ağaçlarının kullanıldığı sahnelerin arkasında). Bu şekilde, bu sorgu ile verilere dokunmadan birçok şey yapma olanağınız olur (bu durumda listedeki veriler). List yöntemi hazırlanan sorguyu alır ve veri kaynağına karşı yürütür.
Bronek

2
Aslında, tüm cevapları okudum ve seninki oy verdiğim cevaptı, çünkü özellikle LINQ / SQL'den bahsetmeden ikisi arasındaki farkı açıkça belirtiyor. LINQ / SQL'e erişmeden ÖNCE tüm bunları bilmek önemlidir. Beğenmek.
BeemerGuy

Bunu açıklamak önemli bir farktır, ancak "beklenen sonuç" gerçekten beklenmez. Tasarımdan ziyade bir çeşit gotcha gibi diyorsunuz.
Neme

@Neme, evet Nasıl IEnumerableçalıştığını anlamadan önce benim beklentim oldu , ama şimdi nasıl biliyorum daha beri değil;)
amd

15

Yapmak istediğiniz tek şey onları numaralandırmaksa IEnumerable,.

Bununla birlikte, orijinal koleksiyonun numaralandırılmasının tehlikeli bir işlem olduğuna dikkat edin - bu durumda ToListönce yapmak isteyeceksiniz . Bu, bellekteki her öğe için yeni bir liste öğesi oluşturacak IEnumerableve yalnızca bir kez numaralandırırsanız daha az performans gösterir - ancak daha güvenlidir ve bazen Listyöntemler kullanışlıdır (örneğin rastgele erişimde).


1
Liste oluşturmanın daha düşük performans anlamına geldiğini söylemenin güvenli olduğundan emin değilim.
Steven Sudit

@ Steven: Gerçekten thecoop ve Chris'in dediği gibi, bazen bir Liste kullanmak gerekebilir. Benim durumumda bunun olmadığı sonucuna vardım. @ Daren: "Bu, bellekteki her öğe için yeni bir liste oluşturacak" ile ne demek istiyorsun? Belki bir "liste girişi" demek istediniz? :: -).
Axonn

@Axonn evet, liste girişinden bahsediyorum. sabit.
Daren Thomas

@Steven içindeki öğeleri yinelemeyi planlıyorsanız IEnumerable, önce bir liste oluşturmak (ve bunun üzerinde yineleme yapmak) öğeler üzerinde iki kez yinelemeniz anlamına gelir . Dolayısıyla, listede daha verimli işlemler yapmak istemiyorsanız, bu gerçekten daha düşük performans anlamına gelir.
Daren Thomas

3
@jerhewet: Tekrarlanan bir diziyi değiştirmek asla iyi bir fikir değildir. Kötü şeyler olacak. Soyutlamalar sızacak. Şeytanlar boyutumuza girecek ve tahribat yaratacak. Yani evet, .ToList()burada yardımcı olur;)
Daren Thomas

5

Yukarıda yayınlanan tüm cevaplara ek olarak, işte benim iki sent. IEnumerable gibi ICollection, ArrayList vb uygulayan List dışında başka birçok türü vardır. Bu nedenle, herhangi bir yöntemin parametresi olarak IEnumerable varsa, herhangi bir toplama türlerini işleve geçirebiliriz. Yani herhangi bir özel uygulama değil soyutlama üzerinde çalışma yöntemimiz olabilir.


1

IEnumerable'un bir listeye dönüştürülemediği birçok durum (sonsuz bir liste veya çok büyük bir liste gibi) vardır. En belirgin örnekler tüm asal sayılar, tüm facebook kullanıcıları ile detayları veya ebay'deki tüm öğelerdir.

Aradaki fark, "Liste" nesnelerinin "şu anda ve şu anda" depolanmasıdır, oysa "IEnumerable" nesneler "birer birer" çalışır. Bu yüzden ebay'deki tüm öğeleri gözden geçirirsem, küçük bir bilgisayarın bile işleyebileceği bir şey olurdu, ancak ".ToList ()" bilgisayarım ne kadar büyük olursa olsun, beni bellekte bırakacaktı. Hiçbir bilgisayar kendi başına bu kadar büyük miktarda veri içeremez ve işleyemez.

[Düzenle] - Söylemeye gerek yok - "şu ya da bu" değil. çoğu zaman aynı sınıfta hem liste hem de IEnumerable kullanmak mantıklı olur. Dünyadaki hiçbir bilgisayar tüm asal sayıları listeleyemez, çünkü tanım gereği bu sonsuz miktarda bellek gerektirir. Ama kolayca a düşünebildiğim class PrimeContainerbir içeriyor IEnumerable<long> primesbilinen nedenlerle de içeren, SortedList<long> _primes. şimdiye kadar hesaplanan tüm asallar. kontrol edilecek bir sonraki asal, sadece mevcut asal değerlere (kareköküne kadar) çalıştırılır. Bu şekilde her ikisini de elde edersiniz - her seferinde bir tane (IEnumerable) ve iyi bir "şimdiye kadar primler" listesi, bu da tüm (sonsuz) listenin oldukça iyi bir yaklaşımıdır.

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.