C # [बंद] में "उपज" कीवर्ड का व्यावहारिक उपयोग


76

लगभग 4 वर्षों के अनुभव के बाद, मैंने एक कोड नहीं देखा है जहाँ उपज कीवर्ड का उपयोग किया जाता है। क्या कोई मुझे इस कीवर्ड का एक व्यावहारिक उपयोग (स्पष्टीकरण के साथ) दिखा सकता है, और यदि ऐसा है, तो अन्य तरीकों से इसे पूरा करना आसान नहीं है?


9
सभी (या कम से कम अधिकांश) LINQ उपज का उपयोग करके कार्यान्वित किया जाता है। इसके अलावा यूनिटी 3 डी फ्रेमवर्क ने इसके लिए कुछ अच्छा उपयोग किया है - इसका उपयोग फ़ंक्शन (उपज स्टेटमेंट पर) को रोकने और बाद में IEnumerable में राज्य का उपयोग करके इसे फिर से शुरू करने के लिए किया जाता है।
दानी

2
यह StackOverflow करने के लिए स्थानांतरित नहीं किया जाना चाहिए?
डैनी व्रॉड

4
@ डैनी - यह स्टैक ओवरफ्लो के लिए उपयुक्त नहीं है, क्योंकि यह सवाल किसी विशिष्ट समस्या को हल करने के लिए नहीं कह रहा है, लेकिन yieldसामान्य रूप से इसका उपयोग करने के बारे में पूछ सकता है।
ChrisF

9
सच में? मैं एक भी ऐप के बारे में नहीं सोच सकता जहाँ मैंने इसका इस्तेमाल नहीं किया है।
Aaronaught

जवाबों:


107

दक्षता

yieldकीवर्ड प्रभावी रूप से संग्रह से अधिक आइटम एक आलसी गणन कि और अधिक कुशल हो सकता है बनाता है। उदाहरण के लिए, यदि आपका foreachलूप 1 मिलियन आइटम के सिर्फ पहले 5 आइटम से अधिक है, तो यह सभी yieldरिटर्न है, और आपने आंतरिक रूप से 1 मिलियन आइटम का संग्रह पहले नहीं बनाया है। इसी तरह आप उन्हीं कार्यकुशलताओं को प्राप्त करने के लिए अपने प्रोग्रामिंग परिदृश्यों में रिटर्न वैल्यू के yieldसाथ उपयोग करना चाहेंगे IEnumerable<T>

एक निश्चित परिदृश्य में प्राप्त दक्षता का उदाहरण

एक पुनरावृत्ति विधि नहीं, एक बड़े संग्रह का संभावित अयोग्य उपयोग,
(मध्यवर्ती संग्रह में बहुत सारे आइटम बनाए गए हैं)

// Method returns all million items before anything can loop over them. 
List<object> GetAllItems() {
    List<object> millionCustomers;
    database.LoadMillionCustomerRecords(millionCustomers); 
    return millionCustomers;
}

// MAIN example ---------------------
// Caller code sample:
int num = 0;
foreach(var itm in GetAllItems())  {
    num++;
    if (num == 5)
        break;
}
// Note: One million items returned, but only 5 used. 

Iterator संस्करण, कुशल
(कोई मध्यवर्ती संग्रह नहीं बनाया गया है)

// Yields items one at a time as the caller's foreach loop requests them
IEnumerable<object> IterateOverItems() {
    for (int i; i < database.Customers.Count(); ++i)
        yield return database.Customers[i];
}

// MAIN example ---------------------
// Caller code sample:
int num = 0;
foreach(var itm in IterateOverItems())  {
    num++;
    if (num == 5)
        break;
}
// Note: Only 5 items were yielded and used out of the million.

कुछ प्रोग्रामिंग परिदृश्यों को सरल बनाएं

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

बस एक उदाहरण दो सूचियों का विलय है:

IEnumerable<object> EfficientMerge(List<object> list1, List<object> list2) {
    foreach(var o in list1) 
        yield return o; 
    foreach(var o in list2) 
        yield return o;
}

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

और जानकारी

yieldकीवर्ड केवल पुनरावर्तक विधि के संदर्भ में इस्तेमाल किया जा सकता (का एक वापसी प्रकार के होने IEnumerable, IEnumerator, IEnumerable<T>, या IEnumerator<T>।) और वहाँ के साथ विशेष संबंध है foreach। Iterators विशेष तरीके हैं। MSDN उपज प्रलेखन और इटरेटर प्रलेखन रोचक जानकारी और अवधारणाओं की व्याख्या के बहुत सारे हैं। इसके साथ सहसंबंधी के लिए सुनिश्चित करें कीवर्ड इसके बारे में बहुत पढ़ने, iterators की अपनी समझ के पूरक के लिए द्वारा।foreach

यह जानने के लिए कि पुनरावृत्तियां अपनी दक्षता कैसे प्राप्त करती हैं, सीएल # संकलक द्वारा उत्पन्न आईएल कोड में रहस्य है। एक पुनरावृत्त विधि के लिए उत्पन्न IL एक नियमित (गैर-पुनरावृत्ति) विधि के लिए उत्पन्न द्रव्यमान से काफी भिन्न होता है। यह लेख (क्या यील्ड कीवर्ड वास्तव में उत्पन्न करता है ?) उस तरह की अंतर्दृष्टि प्रदान करता है।


2
वे एल्गोरिदम के लिए विशेष रूप से उपयोगी हैं जो एक (संभवतः लंबा) अनुक्रम लेते हैं और एक और एक उत्पन्न करते हैं जहां मैपिंग एक-से-एक नहीं है। इसका एक उदाहरण बहुभुज कतरन है; किसी भी विशेष बढ़त कई या कोई भी बढ़त उत्पन्न कर सकता है एक बार क्लिप। Iterators यह व्यक्त करने के लिए बहुत आसान बनाते हैं, और उन्हें लिखना सबसे अच्छा तरीका है।
डोनाल्ड फैलो

+1 बहुत बेहतर जवाब जैसा कि मैंने नीचे लिखा था। अब मैंने यह भी सीखा कि बेहतर प्रदर्शन के लिए पैदावार अच्छी है।
Jan_V

3
एक बार, मैंने बाइनरी नेटवर्क प्रोटोकॉल के लिए पैकेट बनाने के लिए उपज का इस्तेमाल किया। यह C # में सबसे स्वाभाविक पसंद था।
ग्योर्गी आंद्रेसेक

4
क्या database.Customers.Count()पूरे ग्राहकों की गणना को आगे नहीं बढ़ाता है, इस प्रकार हर वस्तु से गुजरने के लिए अधिक कुशल कोड की आवश्यकता होती है?
स्टीफन

5
मुझे गुदा कहो, लेकिन वह है विलय, विलय नहीं। (और
लिन्क में

4

कुछ समय पहले मेरे पास एक व्यावहारिक उदाहरण था, मान लेते हैं कि आपके पास इस तरह की स्थिति है:

List<Button> buttons = new List<Button>();
void AddButtons()
{
   for ( int i = 0; i <= 10; i++ ) {
      var button = new Button();
      buttons.Add(button);
      button.Click += (sender, e) => 
          MessageBox.Show(String.Format("You clicked button number {0}", ???));
   }
}

बटन ऑब्जेक्ट को संग्रह में अपनी स्थिति नहीं पता है। एक ही सीमा Dictionary<T>या अन्य प्रकार के संग्रह के लिए लागू होती है ।

यहां yieldकीवर्ड का उपयोग करके मेरा समाधान है :

interface IHasId { int Id { get; set; } }

class IndexerList<T>: List<T>, IEnumerable<T> where T: IHasId
{
   List<T> elements = new List<T>();
   new public void Clear() { elements.Clear(); }
   new public void Add(T element) { elements.Add(element); }
   new public int Count { get { return elements.Count; } }    
   new public IEnumerator<T> GetEnumerator()
   {
      foreach ( T c in elements )
         yield return c;
   }

   new public T this[int index]
   {
      get
      {
         foreach ( T c in elements ) {
            if ( (int)c.Id == index )
               return c;
         }
         return default(T);
      }
   }
}

और यही मैं इसका उपयोग करता हूं:

class ButtonWithId: Button, IHasId
{
   public int Id { get; private set; }
   public ButtonWithId(int id) { this.Id = id; }
}

IndexerList<ButtonWithId> buttons = new IndexerList<ButtonWithId>();
void AddButtons()
{
   for ( int i = 10; i <= 20; i++ ) {
      var button = new ButtonWithId(i);
      buttons.Add(button);
      button.Click += (sender, e) => 
         MessageBox.Show(String.Format("You clicked button number {0}", ( (ButtonWithId)sender ).Id));
   }
}

forअनुक्रमणिका खोजने के लिए मुझे अपने संग्रह पर लूप बनाने की आवश्यकता नहीं है । मेरे बटन में एक आईडी है और इसे इंडेक्स के रूप में भी उपयोग किया जाता है IndexerList<T>, इसलिए आप किसी भी अनावश्यक आईडी या इंडेक्स से बचते हैं - यही मुझे पसंद है! सूचकांक / आईडी एक मनमाना संख्या हो सकती है।


2

एक व्यावहारिक उदाहरण यहां मिल सकता है:

http://www.ytechie.com/2009/02/using-c-yield-for-readability-and-performance.html

मानक कोड से अधिक उपज का उपयोग करने के कई फायदे हैं:

  • यदि सूची बनाने के लिए पुनरावृत्त का उपयोग किया जाता है तो आप रिटर्न प्राप्त कर सकते हैं और कॉल करने वाला यह तय कर सकता है कि वह सूची में वह परिणाम चाहता है या नहीं।
  • कॉल करने वाला भी पुनरावृत्ति को रद्द करने का फैसला कर सकता है एक कारण है कि आप क्या कर रहे हैं के दायरे से बाहर है।
  • कोड थोड़ा छोटा है।

हालाँकि, जैसा कि Jan_V ने कहा (बस मुझे कुछ सेकंड के लिए हरा दें :-) आप इसके बिना रह सकते हैं क्योंकि आंतरिक रूप से कंपाइलर दोनों मामलों में लगभग समान कोड का उत्पादन करेगा।


1

यहाँ एक उदाहरण है:

https://bitbucket.org/ant512/workingweek/src/a745d02ba16f/source/WorkingWeek/Week.cs#cl-158

वर्ग एक कार्य सप्ताह के आधार पर तिथि गणना करता है। मैं कक्षा का एक उदाहरण बता सकता हूं कि बॉब हर हफ्ते 9:30 से 17:30 तक काम करता है, दोपहर के भोजन के लिए 12:30 बजे एक घंटे का ब्रेक होता है। इस ज्ञान के साथ, AscendingShifts () फ़ंक्शन आपूर्ति की गई तिथियों के बीच कार्यशील शिफ्ट ऑब्जेक्ट प्राप्त करेगा। इस साल 1 जनवरी और 1 फरवरी के बीच बॉब के सभी कार्यशील बदलावों को सूचीबद्ध करने के लिए, आप इसे इस तरह उपयोग करेंगे:

foreach (var shift in week.AscendingShifts(new DateTime(2011, 1, 1), new DateTime(2011, 2, 1)) {
    Console.WriteLine(shift);
}

वर्ग वास्तव में एक संग्रह पर पुनरावृति नहीं करता है। हालांकि, दो तिथियों के बीच बदलाव को एक संग्रह के रूप में माना जा सकता है। yieldऑपरेटर यह संग्रह में ही बनाने के बिना इस कल्पना संग्रह से अधिक पुनरावृति करने के लिए संभव बनाता है।


1

मेरे पास एक छोटी सी db डेटा लेयर है जिसमें एक commandवर्ग है जिसमें आप SQL कमांड टेक्स्ट, कमांड टाइप सेट करते हैं, और 'कमांड पैरामीटर्स' का IEnumerable लौटाते हैं।

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

तो एक फ़ंक्शन है जो इस तरह दिखता है:

IEnumerable<DbParameter> GetParameters()
{
    // here i do something like

    yield return new DbParameter { name = "@Age", value = this.Age };

    yield return new DbParameter { name = "@Name", value = this.Name };
}

इस वर्ग को जो वर्ग विरासत में मिला commandहै, उसके गुण हैं Ageऔर Name

फिर आप किसी commandऑब्जेक्ट को उसके गुणों से भर सकते हैं और उसे एक dbइंटरफ़ेस पर पास कर सकते हैं जो वास्तव में कमांड कॉल करता है।

सब सब में यह एसक्यूएल आदेशों के साथ काम करने और उन्हें टाइप रखने के लिए वास्तव में आसान बनाता है।


1

हालांकि विलय का मामला पहले ही स्वीकृत जवाब में शामिल हो चुका है, मैं आपको पैदावार-मर्ज परमिशन एक्सटेंशन विधि ™ दिखाता हूं:

public static IEnumerable<T> AppendParams<T>(this IEnumerable<T> a, params T[] b)
{
    foreach (var el in a) yield return el;
    foreach (var el in b) yield return el;
}

मैं इसका उपयोग नेटवर्क प्रोटोकॉल के पैकेट बनाने के लिए करता हूं:

static byte[] MakeCommandPacket(string cmd)
{
    return
        header
        .AppendParams<byte>(0, 0, 1, 0, 0, 1, 0x92, 0, 0, 0, 0)
        .AppendAscii(cmd)
        .MarkLength()
        .MarkChecksum()
        .ToArray();
}

MarkChecksumविधि, उदाहरण के लिए, इस तरह दिखता है। और इसके पास एक yieldभी है:

public static IEnumerable<byte> MarkChecksum(this IEnumerable<byte> data, int pos = 6)
{
    foreach (byte b in data)
    {
        yield return pos-- == 0 ? (byte)data.Sum(z => z) : b;
    }
}

लेकिन जब वे अलग गणना प्रक्रिया को ट्रिगर करते हैं तो समन विधि में सम () जैसे कुल तरीकों का उपयोग करते समय सावधान रहें।


1

लोचदार खोज .NET उदाहरण रेपो में yield returnएक संग्रह का उपयोग करने के लिए एक निर्दिष्ट आकार के साथ कई संग्रह में विभाजन का उपयोग करने का एक बड़ा उदाहरण है :

https://github.com/elastic/elasticsearch-net-example/blob/master/src/NuSearch.Domain/Extensions/PartitionExtension.cs

public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> source, int size)
    {
        T[] array = null;
        int count = 0;
        foreach (T item in source)
        {
            if (array == null)
            {
                array = new T[size];
            }
            array[count] = item;
            count++;
            if (count == size)
            {
                yield return new ReadOnlyCollection<T>(array);
                array = null;
                count = 0;
            }
        }
        if (array != null)
        {
            Array.Resize(ref array, count);
            yield return new ReadOnlyCollection<T>(array);
        }
    }

0

Jan_V के उत्तर पर विस्तार करते हुए, मैंने इससे संबंधित एक वास्तविक दुनिया का मामला देखा:

मुझे FindFirstFile / FindNextFile के कर्नेल 32 संस्करणों का उपयोग करने की आवश्यकता है। आप पहली कॉल से एक हैंडल प्राप्त करते हैं और इसे बाद की सभी कॉलों को फीड करते हैं। इसे एक एन्यूमरेटर में लपेटें और आपको कुछ ऐसा मिलता है जिसका आप सीधे उपयोग कर सकते हैं।

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