Sıradan bir IEnumerable kullanabildiğimde neden verim anahtar sözcüğünü kullanmalıyım?


171

Bu kod verildiğinde:

IEnumerable<object> FilteredList()
{
    foreach( object item in FullList )
    {
        if( IsItemInPartialList( item ) )
            yield return item;
    }
}

Neden sadece bu şekilde kodlamamalıyım ?:

IEnumerable<object> FilteredList()
{
    var list = new List<object>(); 
    foreach( object item in FullList )
    {
        if( IsItemInPartialList( item ) )
            list.Add(item);
    }
    return list;
}

yieldAnahtar kelimenin ne yaptığını anlıyorum . Derleyiciye belirli bir şey (bir yineleyici) oluşturmasını söyler. Ama neden kullanıyorsunuz? Biraz daha az kod olmasının yanı sıra, benim için ne yapar?


28
Bu sadece bir örnektir farkındayım ama gerçekten, kod gerektiği şöyle yazılabilir: FullList.Where(IsItemInPartialList):)
BlueRaja - Dany Pflughoeft

Yanıtlar:


241

Kullanmak yieldkoleksiyonu tembel hale getirir .

Diyelim ki ilk beş maddeye ihtiyacınız var. Yolunda , ilk beş öğeyi almak için tüm listeyi dolaşmam gerekiyor. İle yield, sadece ilk beş öğe arasında döngü.


14
Kullanımın FullList.Where(IsItemInPartialList)da tembel olacağını unutmayın . Sadece, çok daha az derleyici oluşturulan özel --- gunk --- kodu gerektirir. Ve daha az geliştirici yazma ve bakım zamanı. (Tabii ki, bu sadece örnekti)
Aralık'ta

4
Bu Linq, değil mi? Linq'in örtü altına çok benzer bir şey yaptığını düşünüyorum.
Robert Harvey

1
Evet, Linq yield returnmümkün olan her yerde gecikmeli yürütme ( ) kullandı .
Chad Schouggins

11
Verim iadesi ifadesi hiçbir zaman yürütülmezse, yine de boş bir toplama sonucu alırsınız, bu nedenle boş bir referans istisnası hakkında endişelenmenize gerek yoktur. çikolata sprinkles ile verim dönüş harika.
Razor

127

Yineleyici blokların yararı tembel olarak çalışmasıdır. Böylece böyle bir filtreleme yöntemi yazabilirsiniz:

public static IEnumerable<T> Where<T>(this IEnumerable<T> source,
                                   Func<T, bool> predicate)
{
    foreach (var item in source)
    {
        if (predicate(item))
        {
            yield return item;
        }
    }
}

Bu, bir akışı istediğiniz kadar filtrelemenize olanak tanır, hiçbir zaman tek bir öğeden daha fazlasını arabelleğe almaz. Örneğin, döndürülen diziden yalnızca ilk değere ihtiyacınız varsa, neden her şeyi yeni bir listeye kopyalamak istiyorsunuz ?

Başka bir örnek olarak, yineleyici bloklarını kullanarak kolayca sonsuz bir akış oluşturabilirsiniz . Örneğin, işte rastgele sayılar dizisi:

public static IEnumerable<int> RandomSequence(int minInclusive, int maxExclusive)
{
    Random rng = new Random();
    while (true)
    {
        yield return rng.Next(minInclusive, maxExclusive);
    }
}

Sonsuz bir diziyi bir listede nasıl saklarsınız?

Benim Edulinq blog serisi yapar LINQ nesnelere örnek bir uygulamasını verir ağır yineleyici blokların kullanımını. LINQ olabileceği yerde tembeldir - ve bir şeyleri bir listeye koymak bu şekilde çalışmaz.


1
Senden hoşlanıp hoşlanmayacağından emin değilim RandomSequence. Bana göre IEnumerable, her şeyden önce foreach ile yineleyebileceğim anlamına gelir, ancak bu açık bir şekilde burada sonsuz bir döngüye yol açacaktır. Bunun IEnumerable konseptinin oldukça tehlikeli bir kullanımı olduğunu düşünürdüm, ama YMMV.
Sebastian Negraszus

5
@SebastianNegraszus: Rasgele sayılar dizisi mantıksal olarak sonsuzdur. IEnumerable<BigInteger>Örneğin, kolayca Fibonacci dizisini temsil eden bir tane oluşturabilirsiniz . foreachBununla birlikte kullanabilirsiniz , ancak IEnumerable<T>bunun sınırlı olacağını garanti eden hiçbir şey yoktur .
Jon Skeet

42

"Liste" koduyla, bir sonraki adıma geçmeden önce tam listeyi işlemeniz gerekir. "Verim" versiyonu işlenen öğeyi hemen bir sonraki adıma geçirir. Bu "sonraki adım" bir ".Take (10)" içeriyorsa, "verim" sürümü yalnızca ilk 10 öğeyi işleyecek ve geri kalanını unutacaktır. "Liste" kodu her şeyi işleyebilirdi.

Bu, çok fazla işlem yapmanız ve / veya işlenecek uzun öğe listeleriniz olması gerektiğinde en fazla farkı gördüğünüz anlamına gelir.


23

yieldListede olmayan öğeleri iade etmek için kullanabilirsiniz . İptal edilene kadar bir liste boyunca sonsuz bir şekilde yinelenebilecek küçük bir örnek.

public IEnumerable<int> GetNextNumber()
{
    while (true)
    {
        for (int i = 0; i < 10; i++)
        {
            yield return i;
        }
    }
}

public bool Canceled { get; set; }

public void StartCounting()
{
    foreach (var number in GetNextNumber())
    {
        if (this.Canceled) break;
        Console.WriteLine(number);
    }
}

Bu yazıyor

0
1
2
3
4
5
6
7
8
9
0
1
2
3
4

...vb. iptal edilinceye kadar konsola.


10
object jamesItem = null;
foreach(var item in FilteredList())
{
   if (item.Name == "James")
   {
       jamesItem = item;
       break;
   }
}
return jamesItem;

Yukarıdaki kod FilteredList () içinde dolaşmak ve item.Name == "James" listesindeki 2. maddeden memnun kalacak şekilde kullanıldığında yield, kullanılan yöntem iki kez elde edilir. Bu tembel bir davranış.

Burada listeyi kullanan yöntem tüm n nesnesini listeye ekler ve listenin tamamını çağıran yönteme geçirir.

Bu, tam olarak IEnumerable ve IList arasındaki farkın vurgulanabileceği bir kullanım durumudur.


7

Kullanımı için gördüğüm en iyi gerçek dünya örneği yieldbir Fibonacci dizisini hesaplamak olacaktır.

Aşağıdaki kodu göz önünde bulundurun:

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(string.Join(", ", Fibonacci().Take(10)));
        Console.WriteLine(string.Join(", ", Fibonacci().Skip(15).Take(1)));
        Console.WriteLine(string.Join(", ", Fibonacci().Skip(10).Take(5)));
        Console.WriteLine(string.Join(", ", Fibonacci().Skip(100).Take(1)));
        Console.ReadKey();
    }

    private static IEnumerable<long> Fibonacci()
    {
        long a = 0;
        long b = 1;

        while (true)
        {
            long temp = a;
            a = b;

            yield return a;

            b = temp + b;
        }
    }
}

Bu geri dönecektir:

1, 1, 2, 3, 5, 8, 13, 21, 34, 55
987
89, 144, 233, 377, 610
1298777728820984005

Bu güzeldir, çünkü sonsuz bir seriyi hızlı ve kolay bir şekilde hesaplamanıza izin verir, size Linq uzantılarını kullanma ve yalnızca ihtiyacınız olanı sorgulama olanağı verir.


5
Fibonacci dizi hesaplamasında "gerçek dünya" diye bir şey göremiyorum.
Nek

Bunun gerçekten "gerçek dünya" olmadığını, ne güzel bir fikir olduğunu kabul ediyorum.
Casey

1

neden [verim] kullanıyorsunuz? Biraz daha az kod olmasının dışında, benim için ne yapar?

Bazen faydalıdır, bazen değil. Tüm veri setinin incelenmesi ve iade edilmesi gerekiyorsa, verimin kullanılmasının herhangi bir faydası olmayacaktır, çünkü yaptığı tek şey ek yük getirmiştir.

Verim gerçekten parladığında, sadece kısmi bir kümenin geri döndüğü zamandır. Bence en iyi örnek sıralamadır. Bu yıldan itibaren bir tarih ve dolar tutarı içeren nesnelerin bir listeniz olduğunu ve yılın ilk avuç (5) kaydını görmek istediğinizi varsayalım.

Bunu yapabilmek için, listenin tarihe göre artan şekilde sıralanması ve ardından ilk 5'in alınması gerekir. Bu, verim olmadan yapıldıysa , son iki tarihin doğru olduğundan emin olmak için tüm listenin sıralanması gerekir.

Bununla birlikte, verimle birlikte, ilk 5 madde belirlendikten sonra sıralama durur ve sonuçlar elde edilebilir. Bu çok zaman kazandırabilir.


0

Getiri beyanı ifadesi bir kerede yalnızca bir öğe iade etmenizi sağlar. Bir listedeki tüm öğeleri topluyorsunuz ve tekrar bir bellek yükü olan listeye dönüyorsunuz.

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.