खोज…


परिचय

जब आप किसी कथन में उपज कीवर्ड का उपयोग करते हैं, तो आप इंगित करते हैं कि विधि, ऑपरेटर, या एक्सेसर प्राप्त करें जिसमें यह दिखाई देता है कि यह एक पुनरावृत्ति है। जब आप एक कस्टम संग्रह प्रकार के लिए IEnumerable और IEnumerator पैटर्न को लागू करते हैं, तो इट्रेटर को परिभाषित करने के लिए पैदावार का उपयोग करके एक स्पष्ट अतिरिक्त वर्ग (एक गणना के लिए राज्य को रखने वाला वर्ग) की आवश्यकता को हटा दिया जाता है।

वाक्य - विन्यास

  • उपज वापसी [TYPE]
  • उपज टूटना

टिप्पणियों

वापसी प्रकार के IEnumerable , IEnumerable<T> , IEnumerator , या IEnumerator<T> प्रकार के साथ एक yield कीवर्ड डालते हुए, संकलक को रिटर्न प्रकार ( IEnumerable या IEnumerator ) का कार्यान्वयन उत्पन्न करने के लिए कहता है, जब लूप किया जाता है, तो रन करता है प्रत्येक परिणाम प्राप्त करने के लिए प्रत्येक "उपज" तक की विधि।

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

yield break उपयोग किसी भी समय अनुक्रम को समाप्त करने के लिए किया जा सकता है।

जैसा कि yield कीवर्ड को रिटर्न टाइप के रूप में एक इटेरियर इंटरफ़ेस प्रकार की आवश्यकता होती है, जैसे कि IEnumerable<T> , आप इसे एक async विधि में उपयोग नहीं कर सकते क्योंकि यह एक Task<IEnumerable<T>> object देता है।

आगे की पढाई

सरल उपयोग

yield कीवर्ड का उपयोग एक फ़ंक्शन को परिभाषित करने के लिए किया जाता है जो एक IEnumerable या IEnumerator (साथ ही उनके व्युत्पन्न जेनेरिक वेरिएंट) लौटाता है, जिनके मान लॉज़र के रूप में उत्पन्न होते हैं जो लौटे संग्रह पर पुनरावृत्ति करता है। टिप्पणी अनुभाग में उद्देश्य के बारे में और पढ़ें।

निम्न उदाहरण एक उपज वापसी कथन एक के अंदर है कि है for पाश।

public static IEnumerable<int> Count(int start, int count)
{
    for (int i = 0; i <= count; i++)
    {
        yield return start + i;
    }
}

तब आप इसे कॉल कर सकते हैं:

foreach (int value in Count(start: 4, count: 10))
{
    Console.WriteLine(value);
}

कंसोल आउटपुट

4
5
6
...
14

.NET फिडल पर लाइव डेमो

foreach स्टेटमेंट बॉडी का प्रत्येक पुनरावृत्ति Count इटैटर फ़ंक्शन को कॉल करता है। इट्रेटर फ़ंक्शन के लिए प्रत्येक कॉल yield return स्टेटमेंट के अगले निष्पादन के लिए होती है, जो लूप के for अगले पुनरावृत्ति के दौरान होती है।

अधिक स्थायी उपयोग

public IEnumerable<User> SelectUsers()
{
    // Execute an SQL query on a database.
    using (IDataReader reader = this.Database.ExecuteReader(CommandType.Text, "SELECT Id, Name FROM Users"))
    {
        while (reader.Read())
        {
            int id = reader.GetInt32(0);
            string name = reader.GetString(1);
            yield return new User(id, name);
        }
    }
}

SQL डेटाबेस से एक IEnumerable<User> प्राप्त करने के अन्य तरीके हैं, ज़ाहिर है - यह सिर्फ यह दर्शाता है कि आप किसी भी चीज़ को चालू करने के लिए yield का उपयोग कर सकते हैं जिसमें IEnumerable<T> में "तत्वों का अनुक्रम" होता है - कोई भी उस पर पुनरावृति कर सकता है। ।

समय से पहले समाप्ति

आप मौजूदा yield विधियों की कार्यक्षमता को एक या एक से अधिक मूल्यों या तत्वों में पारित करके विस्तारित कर सकते हैं, जो फ़ंक्शन के भीतर एक समाप्ति स्थिति को परिभाषित कर सकते हैं, ताकि आंतरिक लूप को निष्पादित करने से रोकने के लिए एक yield break जा सके।

public static IEnumerable<int> CountUntilAny(int start, HashSet<int> earlyTerminationSet)
{
    int curr = start;

    while (true)
    {
        if (earlyTerminationSet.Contains(curr))
        {
            // we've hit one of the ending values
            yield break;
        }

        yield return curr;

        if (curr == Int32.MaxValue)
        {
            // don't overflow if we get all the way to the end; just stop
            yield break;
        }

        curr++;
    }
}

उपरोक्त विधि start स्थिति से तब तक पुनरावृत्त होगी, जब तक कि earlyTerminationSet भीतर का कोई मान सामना नहीं earlyTerminationSet था।

// Iterate from a starting point until you encounter any elements defined as 
// terminating elements
var terminatingElements = new HashSet<int>{ 7, 9, 11 };
// This will iterate from 1 until one of the terminating elements is encountered (7)
foreach(var x in CountUntilAny(1,terminatingElements))
{
    // This will write out the results from 1 until 7 (which will trigger terminating)
    Console.WriteLine(x);
}

आउटपुट:

1
2
3
4
5
6

.NET फिडल पर लाइव डेमो

सही ढंग से तर्कों की जाँच

रिटर्न मान की गणना होने तक एक पुनरावृत्ति विधि निष्पादित नहीं की जाती है। इसलिए यह अच्छा है कि पुनरावृति के बाहर पूर्व शर्त जोर देना।

public static IEnumerable<int> Count(int start, int count)
{
    // The exception will throw when the method is called, not when the result is iterated
    if (count < 0)
        throw new ArgumentOutOfRangeException(nameof(count));

    return CountCore(start, count);
}

private static IEnumerable<int> CountCore(int start, int count)
{
    // If the exception was thrown here it would be raised during the first MoveNext()
    // call on the IEnumerator, potentially at a point in the code far away from where
    // an incorrect value was passed.
    for (int i = 0; i < count; i++)
    {
        yield return start + i;
    }
}

कॉलिंग कोड (उपयोग):

// Get the count
var count = Count(1,10);
// Iterate the results
foreach(var x in count)
{
    Console.WriteLine(x);
}

आउटपुट:

1
2
3
4
5
6
7
8
9
10

.NET फिडल पर लाइव डेमो

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

इसका मतलब है आप अमान्य तर्क के बारे में पता नहीं मिलेगा (गुजर null आदि) जब आप पहली बार विधि कॉल (क्योंकि उस राज्य मशीन बनाता है), केवल जब आप कोशिश करते हैं और पहुँच पहला तत्व (क्योंकि उसके बाद ही भीतर कोड करता है विधि राज्य मशीन द्वारा चलाई जाती है)। इसे एक सामान्य विधि में लपेटकर जो पहले तर्कों की जाँच करता है, जब आप विधि को बुलाते हैं तो आप उन्हें जाँच सकते हैं। यह तेजी से असफल होने का एक उदाहरण है।

C # 7+ का उपयोग करते समय, CountCore फ़ंक्शन को स्थानीय फ़ंक्शन के रूप में Count फ़ंक्शन में आसानी से छिपाया जा सकता है । उदाहरण यहाँ देखें।

एक और Enumerable वापसी एक विधि Enumerable लौटने के भीतर

public IEnumerable<int> F1()
{
    for (int i = 0; i < 3; i++)
        yield return i;

    //return F2(); // Compile Error!!
    foreach (var element in F2())
        yield return element;
}

public int[] F2()
{
    return new[] { 3, 4, 5 };
}

आलसी मूल्यांकन

केवल जब foreach स्टेटमेंट अगले आइटम पर जाता है, तो इट्रेटर ब्लॉक अगले yield स्टेटमेंट तक का मूल्यांकन करता है।

निम्नलिखित उदाहरण पर विचार करें:

private IEnumerable<int> Integers()
{
    var i = 0;
    while(true)
    {
        Console.WriteLine("Inside iterator: " + i);
        yield return i;
        i++;
    }
}

private void PrintNumbers()
{
    var numbers = Integers().Take(3);
    Console.WriteLine("Starting iteration");

    foreach(var number in numbers)
    {
        Console.WriteLine("Inside foreach: " + number);
    }
}

यह आउटपुट होगा:

यात्रा शुरू करना
अंदर चलने वाला: 0
अंदर की तरफ: 0
अंदर चलने वाला: १
अंदर की तरफ: 1
अंदर चलने वाला: २
अंदर की ओर: 2

डेमो देखें

एक परिणाम के रूप में:

  • "आरंभिक पुनरावृति" को पहले प्रिंट किया जाता है, भले ही इटेरेटर विधि को लाइन प्रिंटिंग से पहले कहा जाता था क्योंकि लाइन Integers().Take(3); वास्तव में चलना शुरू नहीं करता है ( IEnumerator.MoveNext() लिए कोई कॉल नहीं किया गया था)
  • इट्रेटर विधि के अंदर एक के बीच वैकल्पिक रूप से सांत्वना देने के लिए मुद्रण लाइनें और पहले के मूल्यांकन करने वाले इट्रेटर विधि के अंदर सभी के बजाय, foreach अंदर एक
  • यह प्रोग्राम .Take() पद्धति के कारण समाप्त हो जाता है, भले ही पुनरावृत्ति विधि में कुछ while true जो कभी भी समाप्त नहीं होता है।

प्रयास करें ... अंत में

यदि किसी पुनरावृत्त विधि में एक try...finally अंदर एक उपज होती है try...finally , तो लौटा हुआ IEnumerator finally विवरण को निष्पादित करेगा जब Dispose को उस पर बुलाया जाता है, जब तक कि मूल्यांकन का वर्तमान बिंदु try ब्लॉक के अंदर है।

समारोह को देखते हुए:

private IEnumerable<int> Numbers()
{
    yield return 1;
    try
    {
        yield return 2;
        yield return 3;
    }
    finally
    {
        Console.WriteLine("Finally executed");
    }
}

कॉल करते समय:

private void DisposeOutsideTry()
{
    var enumerator = Numbers().GetEnumerator();

    enumerator.MoveNext();
    Console.WriteLine(enumerator.Current);
    enumerator.Dispose();
}

फिर यह प्रिंट करता है:

1

डेमो देखें

कॉल करते समय:

private void DisposeInsideTry()
{
    var enumerator = Numbers().GetEnumerator();

    enumerator.MoveNext();
    Console.WriteLine(enumerator.Current);
    enumerator.MoveNext();
    Console.WriteLine(enumerator.Current);
    enumerator.Dispose();
}

फिर यह प्रिंट करता है:

1
2
अंत में अंजाम दिया गया

डेमो देखें

एक IEnumerator बनाने के लिए उपज का उपयोग करना जब लागू IEnumerable

IEnumerable<T> इंटरफ़ेस में एक एकल विधि है, GetEnumerator() , जो एक IEnumerator<T> लौटाता है।

जबकि yield कीवर्ड का उपयोग सीधे IEnumerable<T> बनाने के लिए किया जा सकता है, इसका उपयोग ठीक उसी तरह से भी किया जा सकता है, जिससे IEnumerator<T> । केवल एक चीज जो बदल जाती है वह है विधि का वापसी प्रकार।

यह उपयोगी हो सकता है अगर हम अपनी कक्षा बनाना चाहते हैं जो IEnumerable<T> लागू करता है:

public class PrintingEnumerable<T> : IEnumerable<T>
{
    private IEnumerable<T> _wrapped;

    public PrintingEnumerable(IEnumerable<T> wrapped)
    {
        _wrapped = wrapped;
    }

    // This method returns an IEnumerator<T>, rather than an IEnumerable<T>
    // But the yield syntax and usage is identical.
    public IEnumerator<T> GetEnumerator()
    {
        foreach(var item in _wrapped)
        {
            Console.WriteLine("Yielding: " + item);
            yield return item;
        }
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

(ध्यान दें कि यह विशेष उदाहरण सिर्फ उदाहरण के लिए है, और IEnumerable<T> लौटाने वाले एकल पुनरावृत्त विधि के साथ अधिक सफाई से लागू किया जा सकता है।)

ईगर मूल्यांकन

yield कीवर्ड संग्रह के आलसी-मूल्यांकन की अनुमति देता है। पूरे संग्रह को स्मृति में जबरन लोड करने को उत्सुक मूल्यांकन कहा जाता है।

निम्न कोड यह दिखाता है:

IEnumerable<int> myMethod()
{
    for(int i=0; i <= 8675309; i++)
    {
        yield return i;
    }
}
...
// define the iterator
var it = myMethod.Take(3);
// force its immediate evaluation
// list will contain 0, 1, 2
var list = it.ToList();

ToList , ToDictionary या ToArray को कॉल ToList , सभी तत्वों को एक संग्रह में प्राप्त करते हुए, गणना के तत्काल मूल्यांकन को मजबूर करेगा।

आलसी मूल्यांकन उदाहरण: फाइबोनैचि संख्या

using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics; // also add reference to System.Numberics

namespace ConsoleApplication33
{
    class Program
    {
        private static IEnumerable<BigInteger> Fibonacci()
        {
            BigInteger prev = 0;
            BigInteger current = 1;
            while (true)
            {
                yield return current;
                var next = prev + current;
                prev = current;
                current = next;
            }
        }

        static void Main()
        {
            // print Fibonacci numbers from 10001 to 10010
            var numbers = Fibonacci().Skip(10000).Take(10).ToArray();
            Console.WriteLine(string.Join(Environment.NewLine, numbers));
        }
    }
}

यह हुड के नीचे कैसे काम करता है (मैं परिणामी डिकंपाइल करने की सलाह देता हूं। आईएल डिसैम्बलर टूल में .exe फ़ाइल):

  1. C # कंपाइलर IEnumerable<BigInteger> और IEnumerator<BigInteger> ( <Fibonacci>d__0 में <Fibonacci>d__0 ) लागू करने वाला एक वर्ग उत्पन्न करता है।
  2. यह वर्ग एक राज्य मशीन को लागू करता है। राज्य में वर्तमान स्थिति और स्थानीय चर के मूल्य शामिल हैं।
  3. सबसे दिलचस्प कोड bool IEnumerator.MoveNext() विधि में हैं। मूल रूप से, MoveNext() क्या करते हैं:
    • वर्तमान स्थिति को पुनर्स्थापित करता है। की तरह चर prev और current हमारी कक्षा में बनने क्षेत्र ( <current>5__2 और <prev>5__1 ILDASM में)। हमारी पद्धति में हमारे दो स्थान हैं ( <>1__state ): पहली बार घुंघराले ब्रेस पर, दूसरा yield return
    • अगली yield return या yield break / } तक कोड निष्पादित करता है।
    • yield return जिसके परिणामस्वरूप मूल्य बच जाता है, इसलिए Current संपत्ति उसे वापस कर सकती है। true लौटा है। इस बिंदु पर वर्तमान राज्य फिर से अगले MoveNext आह्वान के लिए सहेजा जाता है।
    • yield break / } विधि सिर्फ false अर्थ लौटाती है yield break पुनरावृत्ति होती है।

यह भी ध्यान दें, कि 10001 वां नंबर 468 बाइट्स का है। राज्य मशीन केवल बचाता current और prev क्षेत्रों के रूप में चर। यदि हम पहले से 10000 वें क्रम में सभी संख्याओं को सहेजना चाहते हैं, तो खपत की गई मेमोरी का आकार 4 मेगाबाइट से अधिक होगा। तो आलसी मूल्यांकन, यदि ठीक से उपयोग किया जाता है, तो कुछ मामलों में स्मृति पदचिह्न को कम कर सकता है।

ब्रेक और यील्ड के बीच का अंतर

का उपयोग करते हुए yield break के रूप में करने का विरोध किया break स्पष्ट रूप में एक सोच सकते हैं के रूप में नहीं हो सकता है। इंटरनेट पर बहुत सारे बुरे उदाहरण हैं जहां दो का उपयोग विनिमेय है और वास्तव में अंतर प्रदर्शित नहीं करता है।

भ्रामक हिस्सा यह है कि दोनों कीवर्ड (या कुंजी वाक्यांश) केवल छोरों के भीतर समझ में आते हैं ( foreach , while ...) तो दूसरे पर एक का चयन करने के लिए कब?

यह महसूस करना महत्वपूर्ण है कि एक बार जब आप प्रभावी तरीके से एक इट्रेटर में विधि को बदल देते हैं तो आप yield कीवर्ड का उपयोग करते हैं । इस तरह की विधि का एकमात्र उद्देश्य इसके तत्वों को परिमित या अनंत संग्रह और उपज (आउटपुट) पर पुनरावृत्त करना है। एक बार उद्देश्य पूरा होने के बाद, विधि के निष्पादन को जारी रखने का कोई कारण नहीं है। कभी-कभी, यह स्वाभाविक रूप से विधि के अंतिम समापन ब्रैकेट के साथ होता है } । लेकिन कभी-कभी, आप समय से पहले विधि को समाप्त करना चाहते हैं। एक सामान्य (गैर-पुनरावृति) विधि में आप return कीवर्ड का उपयोग करेंगे। लेकिन आप एक पुनरावृत्त में return का उपयोग नहीं कर सकते, आपको yield break का उपयोग करना होगा। दूसरे शब्दों में, एक इट्रेटर के लिए yield break एक मानक विधि के लिए return के समान है। जबकि, break स्टेटमेंट सिर्फ निकटतम लूप को समाप्त करता है।

आइए देखते हैं कुछ उदाहरण:

    /// <summary>
    /// Yields numbers from 0 to 9
    /// </summary>
    /// <returns>{0,1,2,3,4,5,6,7,8,9}</returns>
    public static IEnumerable<int> YieldBreak()
    {
        for (int i = 0; ; i++)
        {
            if (i < 10)
            {
                // Yields a number
                yield return i;
            }
            else
            {
                // Indicates that the iteration has ended, everything 
                // from this line on will be ignored
                yield break;
            }
        }
        yield return 10; // This will never get executed
    }
    /// <summary>
    /// Yields numbers from 0 to 10
    /// </summary>
    /// <returns>{0,1,2,3,4,5,6,7,8,9,10}</returns>
    public static IEnumerable<int> Break()
    {
        for (int i = 0; ; i++)
        {
            if (i < 10)
            {
                // Yields a number
                yield return i;
            }
            else
            {
                // Terminates just the loop
                break;
            }
        }
        // Execution continues
        yield return 10;
    }


Modified text is an extract of the original Stack Overflow Documentation
के तहत लाइसेंस प्राप्त है CC BY-SA 3.0
से संबद्ध नहीं है Stack Overflow