Linq yüzeyde göründüğünden daha mı verimli?


13

Böyle bir şey yazarsam:

var things = mythings
    .Where(x => x.IsSomeValue)
    .Where(y => y.IsSomeOtherValue)

Bu aynı mı:

var results1 = new List<Thing>();
foreach(var t in mythings)
    if(t.IsSomeValue)
        results1.Add(t);

var results2 = new List<Thing>();
foreach(var t in results1)
    if(t.IsSomeOtherValue)
        results2.Add(t);

Ya da kapakların altında daha çok çalışan bir sihir var mı:

var results = new List<Thing>();
foreach(var t in mythings)
    if(t.IsSomeValue && t.IsSomeOtherValue)
        results.Add(t);

Yoksa tamamen farklı bir şey mi?


4
Bunu ILSpy'da görebilirsiniz.
ChaosPandion

1
ILSpy'ın arkadaşınız olduğu birinci fakat ikinci ChaosPandion'un cevabından çok ikinci örneğe benziyor.
Michael

Yanıtlar:


27

LINQ sorguları tembeldir . Bu kod anlamına gelir:

var things = mythings
    .Where(x => x.IsSomeValue)
    .Where(y => y.IsSomeOtherValue);

çok az şey yapar. Orijinal numaralandırılabilir ( mythings) yalnızca elde edilen numaralandırılabilir ( things), örneğin bir foreachdöngü .ToList()veya tarafından tüketildiğinde numaralandırılır .ToArray().

Eğer ararsanız things.ToList(), bu ikinci kodunuza kabaca eşdeğerdir, belki de numaralandırıcılardan bazılarının (genellikle önemsiz) yükü vardır.

Benzer şekilde, bir foreach döngüsü kullanıyorsanız:

foreach (var t in things)
    DoSomething(t);

Performansta şuna benzer:

foreach (var t in mythings)
    if (t.IsSomeValue && t.IsSomeOtherValue)
        DoSomething(t);

Numaralandırılabilirler için tembellik yaklaşımının performans avantajlarından bazıları (tüm sonuçları hesaplamak ve bir listede saklamak yerine) çok az bellek kullanmasıdır (çünkü bir seferde yalnızca bir sonuç depolanır) ve önemli bir şey olmaması - ön maliyet.

Numaralandırılabilir yalnızca kısmen numaralandırılmışsa, bu özellikle önemlidir. Bu kodu düşünün:

things.First();

LINQ'nun uygulanma şekli, mythingsyalnızca konum koşullarınıza uyan ilk öğeye kadar numaralandırılır. Bu öğe listenin başındaysa, bu büyük bir performans artışı olabilir (örneğin O (n) yerine O (1)).


1
LINQ ve eşdeğer kod foreachkullanımı arasındaki bir performans farkı, LINQ'nun bazı ek yükleri olan temsilci çağrılarını kullanmasıdır. Bu, koşullar çok hızlı bir şekilde yürütüldüğünde (genellikle yaptıkları) önemli olabilir.
svick

2
Enumerator tepegöz ile kastediyorum. Bazı (nadir) durumlarda bir sorun olabilir, ancak benim deneyimime göre çok sık olmayan bir şeydir - genellikle geçen süre başlamak için çok küçüktür veya yaptığınız diğer işlemlerden çok daha ağır basar.
Cyanfish

Linq'in tembel değerlendirmesinin kötü bir kısıtlaması, ToListveya gibi yöntemler dışında bir numaralandırmanın "anlık görüntüsünü" almanın hiçbir yolu olmamasıdır ToArray. Böyle bir şey düzgün bir şekilde oluşturulmuş IEnumerableolsaydı, bir listeden gelecekte her şeyi üretmek zorunda kalmadan değişebilecek herhangi bir yönü "anlık olarak" görüntülemesini istemek mümkün olurdu.
supercat

7

Aşağıdaki kod:

var things = mythings
    .Where(x => x.IsSomeValue)
    .Where(y => y.IsSomeOtherValue);

Hiçbir şeye eşdeğer değildir, tembel değerlendirme nedeniyle hiçbir şey olmayacaktır.

var things = mythings
    .Where(x => x.IsSomeValue)
    .Where(y => y.IsSomeOtherValue)
    .ToList();

Farklı çünkü değerlendirme başlatılacak.

Her öğe mythingsbirinciye verilecektir Where. Geçerse, ikincisine verilecektir Where. Geçerse, çıktının bir parçası olacaktır.

Yani bu daha çok şuna benziyor:

var results = new List<Thing>();
foreach(var t in mythings)
{
    if(t.IsSomeValue)
    {
        if(t.IsSomeOtherValue)
        {
            results.Add(t);
        }
    }
}

7

Ertelenmiş yürütme bir yana (diğer cevaplar zaten açıklıyor, başka bir ayrıntıya işaret edeceğim), daha çok ikinci örneğinizdeki gibi.

Sadece çağrı düşünelim ToListüzerinde things.

Getirilerin uygulanması Enumerable.Wherea Enumerable.WhereListIterator. Aradığınızda Wherebu konuda WhereListIterator(aka zincirleme Where, artık çağrı -calls) Enumerable.Where, ama Enumerable.WhereListIterator.Whereaslında yüklemleri birleştirir (kullanarak Enumerable.CombinePredicates).

Yani daha çok if(t.IsSomeValue && t.IsSomeOtherValue).


"bir Enumerable.WhereListIterator döndürür" beni tıklattı. Muhtemelen çok basit bir kavram, ama ILSpy ile bu yüzden gözden kaçırıyordum. Teşekkürler
ConditionRacer

Daha ayrıntılı bir analizle ilgileniyorsanız Jon Skeet'in bu optimizasyonu yeniden uygulamasına bakın .
13'te

1

Hayır, aynı değil. Örnekte thingsbir olduğunu IEnumerablebu noktada hala sadece bir yineleyici, gerçek bir dizi veya liste olan. Üstelik thingskullanılmadığı için, döngü asla değerlendirilmez. Tür IEnumerable, yieldLinq talimatları tarafından -ed ile yinelenen ve daha fazla talimatla bunları daha fazla işlemek için izin verir , yani sonunda sadece bir döngü var demektir.

Ancak, .ToArray()veya gibi bir talimat eklediğinizde .ToList(), gerçek bir veri yapısının oluşturulmasını sipariş edersiniz, böylece zincirinize sınırlar koyarsınız.

Bu ilgili SO sorusuna bakın: /programming/2789389/how-do-i-implement-ienumerable

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.