उपज कीवर्ड का उपयोग क्यों करें, जब मैं सिर्फ एक साधारण IEnumerable का उपयोग कर सकता हूं?


171

इस कोड को दिया:

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

मुझे इसे इस तरह कोड क्यों नहीं करना चाहिए ?:

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

मैं समझता हूं कि yieldकीवर्ड क्या करता है। यह संकलक को एक निश्चित प्रकार की चीज (एक पुनरावृत्ति) बनाने के लिए कहता है। लेकिन इसका उपयोग क्यों करें? इसके अलावा यह थोड़ा कम कोड होने के कारण, यह मेरे लिए क्या है?


28
मुझे लगता है कि यह सिर्फ एक उदाहरण है, लेकिन वास्तव में, कोड इस तरह लिखा जाना चाहिए : FullList.Where(IsItemInPartialList):)
BlueRaja - डैनी Pflughoeft

जवाबों:


241

उपयोग yieldसंग्रह को आलसी बनाता है

मान लीजिए कि आपको पहले पांच वस्तुओं की आवश्यकता है। आपका तरीका, मुझे पहले पांच आइटम प्राप्त करने के लिए पूरी सूची के माध्यम से लूप करना होगा। के साथ yield, मैं केवल पहले पांच वस्तुओं के माध्यम से लूप करता हूं।


14
ध्यान दें कि उपयोग FullList.Where(IsItemInPartialList)करना आलसी के समान होगा। केवल, इसके लिए बहुत कम संकलक उत्पन्न कस्टम --- गंक --- कोड की आवश्यकता होती है। और कम डेवलपर समय लेखन और रखरखाव। (बेशक, यह सिर्फ उदाहरण था)
sehe

4
यह Linq है, है ना? मुझे लगता है कि Linq कवर के तहत बहुत समान है।
रॉबर्ट हार्वे

1
हां, लिनक ने विलंबित निष्पादन ( yield return) का उपयोग किया जहां कभी संभव हो।
चाड शाउगिन्स

11
यह मत भूलो कि यदि उपज वापसी विवरण कभी निष्पादित नहीं होता है, तो आपको अभी भी एक खाली संग्रह परिणाम मिलता है, इसलिए अशक्त संदर्भ अपवाद के बारे में चिंता करने की कोई आवश्यकता नहीं है। पैदावार वापसी चॉकलेट के साथ बहुत बढ़िया है।
रेजर

127

पुनरावृत्त ब्लॉकों का लाभ यह है कि वे आलसी होकर काम करते हैं। तो आप इस तरह से एक फ़िल्टरिंग विधि लिख सकते हैं:

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;
        }
    }
}

यह आपको तब तक एक स्ट्रीम फ़िल्टर करने की अनुमति देगा, जब तक आप एक बार में एक से अधिक आइटम बफरिंग नहीं करते। यदि आपको केवल दिए गए अनुक्रम से पहले मूल्य की आवश्यकता है, उदाहरण के लिए, आप सब कुछ एक नई सूची में क्यों कॉपी करना चाहेंगे ?

एक अन्य उदाहरण के रूप में, आप पुनरावृत्त ब्लॉकों का उपयोग करके आसानी से एक अनंत धारा बना सकते हैं । उदाहरण के लिए, यहां यादृच्छिक संख्याओं का क्रम है:

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

आप एक सूची में अनंत अनुक्रम कैसे संग्रहीत करेंगे?

My Edulinq ब्लॉग श्रृंखला , LINQ का एक नमूना कार्यान्वयन वस्तुओं को देती है जो पुनरावृत्त ब्लॉकों का भारी उपयोग करती है। LINQ मौलिक रूप से आलसी है जहाँ यह हो सकता है - और चीजों को सूची में रखना बस उस तरह से काम नहीं करता है।


1
मुझे यकीन नहीं है कि आपको पसंद करना है RandomSequenceया नहीं। मेरे लिए, IEnumerable का अर्थ है-प्यास और सबसे महत्वपूर्ण- कि मैं foreach से पुनरावृति कर सकता हूं, लेकिन इससे स्पष्ट रूप से यहां एक अनन्त लूप बन जाएगा। मैं इस IEnumerable अवधारणा का एक बहुत खतरनाक दुरुपयोग पर विचार करेंगे, लेकिन YMMV।
सेबस्टियन नेग्रास्ज़स

5
@ सेबैस्टियननग्रासज़स: यादृच्छिक संख्याओं का एक क्रम तार्किक रूप से अनंत है। IEnumerable<BigInteger>उदाहरण के लिए, आप आसानी से फाइबोनैचि अनुक्रम का प्रतिनिधित्व कर सकते हैं । आप foreachइसके साथ उपयोग कर सकते हैं , लेकिन IEnumerable<T>गारंटी के बारे में कुछ भी नहीं है कि यह परिमित होगा।
जॉन स्कीट

42

"सूची" कोड के साथ, आपको अगले चरण पर पास करने से पहले पूरी सूची को संसाधित करना होगा। "उपज" संस्करण संसाधित आइटम को अगले चरण में तुरंत पास करता है। यदि उस "अगले चरण" में एक ".टेक (10)" है, तो "उपज" संस्करण में केवल पहले 10 आइटम संसाधित होंगे और बाकी के बारे में भूल जाएंगे। "सूची" कोड ने सब कुछ संसाधित किया होगा।

इसका मतलब है कि आपको सबसे अधिक अंतर तब दिखाई देता है जब आपको बहुत अधिक प्रसंस्करण करने की आवश्यकता होती है और / या संसाधित करने के लिए वस्तुओं की लंबी सूची होती है।


23

आप उन yieldवस्तुओं को वापस करने के लिए उपयोग कर सकते हैं जो किसी सूची में नहीं हैं। यहाँ एक छोटा सा नमूना है जो रद्द होने तक सूची के माध्यम से असीम रूप से पुनरावृति कर सकता है।

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

यह लिखता है

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

...आदि। रद्द करने तक कंसोल पर जाएं।


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

जब उपर्युक्त कोड को FilteredList () और संभालने वाले आइटम के माध्यम से उपयोग किया जाता है। नाम == "जेम्स" सूची में 2 आइटम पर संतुष्ट हो जाएगा, तो उपयोग करने की विधि yieldदो बार निकलेगी । यह एक आलसी व्यवहार है।

जहां सूची का उपयोग करने का तरीका सूची में सभी n वस्तुओं को जोड़ देगा और पूरी सूची को कॉलिंग विधि में पास कर देगा।

यह वास्तव में एक उपयोग मामला है जहां IEnumerable और IList के बीच अंतर को उजागर किया जा सकता है।


7

सबसे अच्छा वास्तविक उदाहरण मैंने देखा है जिसके उपयोग के yieldलिए एक फाइबोनैचि अनुक्रम की गणना होगी।

निम्नलिखित कोड पर विचार करें:

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;
        }
    }
}

यह लौटेगा:

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

यह अच्छा है क्योंकि यह आपको एक अनंत श्रृंखला को जल्दी और आसानी से गणना करने की अनुमति देता है, जिससे आपको Linq एक्सटेंशन का उपयोग करने की अनुमति मिलती है और आपको केवल उसी चीज़ की क्वेरी करनी होती है जो आपको चाहिए।


5
मैं फिबोनाची अनुक्रम गणना में कुछ भी "वास्तविक दुनिया" नहीं देख सकता।
नेक

मैं मानता हूं कि यह वास्तव में "वास्तविक दुनिया" नहीं है, लेकिन यह एक अच्छा विचार है।
केसी

1

क्यों [उपज] का उपयोग करें? इसके अलावा यह थोड़ा कम कोड होने के कारण, यह मेरे लिए क्या है?

कभी-कभी यह उपयोगी है, कभी-कभी नहीं। यदि डेटा के पूरे सेट की जांच की जानी चाहिए और वापस आ जाना चाहिए, तो उपज का उपयोग करने में कोई लाभ नहीं होने वाला है क्योंकि यह सब किया था ओवरहेड शुरू किया गया था।

जब उपज वास्तव में चमकती है, जब केवल एक आंशिक सेट वापस किया जाता है। मुझे लगता है कि सबसे अच्छा उदाहरण छँट रहा है। मान लें कि आपके पास इस वर्ष की दिनांक और डॉलर राशि वाली वस्तुओं की एक सूची है और आप वर्ष के पहले मुट्ठी भर (5) रिकॉर्ड देखना चाहेंगे।

इसे पूरा करने के लिए, सूची को तिथि के अनुसार क्रमबद्ध किया जाना चाहिए, और फिर पहले 5 को लिया जाना चाहिए। यदि यह बिना उपज के किया गया था, तो पूरी सूची को क्रमबद्ध करना होगा, यह सुनिश्चित करने के लिए कि अंतिम दो तिथियां क्रम में थीं।

हालांकि, उपज के साथ, एक बार पहले 5 आइटम छँटाई स्टॉप स्थापित किए गए हैं और परिणाम उपलब्ध हैं। इससे बड़ी मात्रा में समय बचाया जा सकता है।


0

यील्ड रिटर्न स्टेटमेंट आपको एक बार में केवल एक आइटम वापस करने की अनुमति देता है। आप एक सूची में सभी आइटम एकत्र कर रहे हैं और फिर से उस सूची को वापस कर रहे हैं, जो एक मेमोरी ओवरहेड है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.