c# - सी#में उपयोग किए जाने वाले उपज कीवर्ड क्या है?




yield (11)

मैं केवल IList के एक टुकड़े का पर्दाफाश कैसे कर सकता हूं <> प्रश्नों में से एक के पास निम्न कोड स्निपेट था:

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

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


अगर मैं इसे सही ढंग से समझता हूं, तो यहां मैं उपज के साथ IENumerable लागू करने वाले फ़ंक्शन के परिप्रेक्ष्य से इसे कैसे वाक्यांश दूंगा।

  • यहां एक है।
  • अगर आपको किसी और की ज़रूरत है तो फिर से कॉल करें।
  • मुझे याद होगा कि मैंने आपको पहले ही क्या दिया है।
  • मुझे केवल तभी पता चलेगा जब मैं आपको दोबारा फोन कर सकता हूं।

अवधारणा को समझने का एक आसान तरीका यहां है: मूल विचार यह है कि, यदि आप एक संग्रह चाहते हैं जिसे आप " foreach " का उपयोग कर सकते हैं, लेकिन संग्रह में आइटम एकत्र करना किसी कारण से महंगा है (जैसे उन्हें डेटाबेस से पूछताछ करना) , और आपको अक्सर पूरे संग्रह की आवश्यकता नहीं होगी, फिर आप एक ऐसा फ़ंक्शन बनाते हैं जो संग्रह को एक समय में एक आइटम बनाता है और इसे उपभोक्ता को वापस लाता है (जो तब संग्रह प्रयास को समाप्त कर सकता है)।

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


उपज कीवर्ड वास्तव में यहां बहुत कुछ करता है। फ़ंक्शन एक ऑब्जेक्ट देता है जो IENumerable इंटरफ़ेस लागू करता है। यदि एक कॉलिंग फ़ंक्शन इस ऑब्जेक्ट पर फ़ोरैच-इन शुरू करता है तो फ़ंक्शन को "उपज" तक दोबारा बुलाया जाता है। यह सी # 2.0 में पेश सिंथैक्टिक चीनी है। पिछले संस्करणों में आपको इस तरह की चीजें करने के लिए अपनी खुद की आईन्यूमेरेबल और आईन्यूमेरेटर ऑब्जेक्ट्स बनाना था।

इस तरह के कोड को समझने का सबसे आसान तरीका उदाहरण में टाइप करना है, कुछ ब्रेकपॉइंट्स सेट करें और देखें कि क्या होता है।

उदाहरण के लिए इसके माध्यम से कदम उठाने का प्रयास करें:

public void Consumer()
{
    foreach(int i in Integers())
    {
        Console.WriteLine(i.ToString());
    }
}

public IEnumerable<int> Integers()
{
    yield return 1;
    yield return 2;
    yield return 4;
    yield return 8;
    yield return 16;
    yield return 16777216;
}

जब आप उदाहरण के माध्यम से कदम उठाते हैं तो आपको इंटेगर्स () रिटर्न पर पहला कॉल मिलेगा। दूसरा कॉल 2 देता है और लाइन "उपज रिटर्न 1" फिर से निष्पादित नहीं होती है।

यहां एक वास्तविक जीवन उदाहरण है

public IEnumerable<T> Read<T>(string sql, Func<IDataReader, T> make, params object[] parms)
{
    using (var connection = CreateConnection())
    {
        using (var command = CreateCommand(CommandType.Text, sql, connection, parms))
        {
            command.CommandTimeout = dataBaseSettings.ReadCommandTimeout;
            using (var reader = command.ExecuteReader())
            {
                while (reader.Read())
                {
                    yield return make(reader);
                }
            }
        }
    }
}

एक सूची या सरणी कार्यान्वयन तुरंत सभी वस्तुओं को लोड करता है जबकि उपज कार्यान्वयन एक स्थगित निष्पादन समाधान प्रदान करता है।

व्यावहारिक रूप से, आवेदन की संसाधन खपत को कम करने के लिए आवश्यक न्यूनतम कार्य करने के लिए अक्सर वांछनीय होता है।

उदाहरण के लिए, हमारे पास एक ऐसा एप्लिकेशन हो सकता है जो डेटाबेस से लाखों रिकॉर्ड संसाधित करता हो। जब हम एक स्थगित निष्पादन पुल-आधारित मॉडल में IENumerable का उपयोग करते हैं तो निम्न लाभ प्राप्त किए जा सकते हैं:

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

उपज का उपयोग करने की तुलना में सूची जैसे पहले संग्रह बनाने के बीच तुलना की तुलना यहां दी गई है।

सूची उदाहरण

    public class ContactListStore : IStore<ContactModel>
    {
        public IEnumerable<ContactModel> GetEnumerator()
        {
            var contacts = new List<ContactModel>();
            Console.WriteLine("ContactListStore: Creating contact 1");
            contacts.Add(new ContactModel() { FirstName = "Bob", LastName = "Blue" });
            Console.WriteLine("ContactListStore: Creating contact 2");
            contacts.Add(new ContactModel() { FirstName = "Jim", LastName = "Green" });
            Console.WriteLine("ContactListStore: Creating contact 3");
            contacts.Add(new ContactModel() { FirstName = "Susan", LastName = "Orange" });
            return contacts;
        }
    }

    static void Main(string[] args)
    {
        var store = new ContactListStore();
        var contacts = store.GetEnumerator();

        Console.WriteLine("Ready to iterate through the collection.");
        Console.ReadLine();
    }

कंसोल आउटपुट
ContactListStore: संपर्क 1 बनाना
ContactListStore: संपर्क 2 बनाना
ContactListStore: संपर्क 3 बनाना
संग्रह के माध्यम से पुन: प्रयास करने के लिए तैयार है।

नोट: पूरे संग्रह को सूची में एक आइटम के बिना भी स्मृति में लोड किया गया था

उपज उदाहरण

public class ContactYieldStore : IStore<ContactModel>
{
    public IEnumerable<ContactModel> GetEnumerator()
    {
        Console.WriteLine("ContactYieldStore: Creating contact 1");
        yield return new ContactModel() { FirstName = "Bob", LastName = "Blue" };
        Console.WriteLine("ContactYieldStore: Creating contact 2");
        yield return new ContactModel() { FirstName = "Jim", LastName = "Green" };
        Console.WriteLine("ContactYieldStore: Creating contact 3");
        yield return new ContactModel() { FirstName = "Susan", LastName = "Orange" };
    }
}

static void Main(string[] args)
{
    var store = new ContactYieldStore();
    var contacts = store.GetEnumerator();

    Console.WriteLine("Ready to iterate through the collection.");
    Console.ReadLine();
}

कंसोल आउटपुट
संग्रह के माध्यम से पुन: प्रयास करने के लिए तैयार है।

नोट: संग्रह बिल्कुल निष्पादित नहीं किया गया था। यह IEnumerable की "स्थगित निष्पादन" प्रकृति के कारण है। किसी आइटम का निर्माण केवल तभी होता है जब यह वास्तव में आवश्यक हो।

जब हम संग्रह में पहला संपर्क प्राप्त करते हैं तो चलो संग्रह को फिर से कॉल करें और व्यवहार को उलट दें।

static void Main(string[] args)
{
    var store = new ContactYieldStore();
    var contacts = store.GetEnumerator();
    Console.WriteLine("Ready to iterate through the collection");
    Console.WriteLine("Hello {0}", contacts.First().FirstName);
    Console.ReadLine();
}

कंसोल आउटपुट
संग्रह के माध्यम से पुन: प्रयास करने के लिए तैयार है
ContactYieldStore: संपर्क 1 बनाना
हैलो बॉब

अच्छा! केवल पहला संपर्क तब बनाया गया जब ग्राहक ने संग्रह से आइटम को "खींच लिया"।


पुनरावृत्ति। यह "कवर के तहत" एक राज्य मशीन बनाता है जो याद करता है कि आप समारोह के प्रत्येक अतिरिक्त चक्र पर कहां थे और वहां से उठाते हैं।


यह आपके ऑब्जेक्ट के लिए एक गणना करने के लिए एक बहुत ही सरल और आसान तरीका है। कंपाइलर एक वर्ग बनाता है जो आपकी विधि को लपेटता है और यह लागू करता है, इस मामले में, Inumerable <object>। उपज कीवर्ड के बिना, आपको एक ऑब्जेक्ट बनाना होगा जो IENumerable <object> लागू करता है।


यह गणनात्मक अनुक्रम का उत्पादन कर रहा है। यह वास्तव में स्थानीय IENumerable अनुक्रम बना रहा है और इसे एक विधि परिणाम के रूप में वापस कर रहा है


यील्ड के दो महान उपयोग हैं,

  1. यह अस्थायी संग्रह बनाने के बिना कस्टम पुनरावृत्ति प्रदान करने में मदद करता है।

  2. यह राज्यपूर्ण पुनरावृत्ति करने में मदद करता है।

दो बिंदुओं से अधिक स्पष्ट रूप से व्याख्या करने के लिए, मैंने एक साधारण वीडियो बनाया है जिसे आप here देख सकते here


सी # उपज कीवर्ड, इसे आसानी से रखने के लिए, कोड के एक समूह को कई कॉलों की अनुमति देता है, जिसे एक पुनरावर्तक के रूप में जाना जाता है, जो जानता है कि इसे करने से पहले कैसे लौटना है, और जब इसे फिर से बुलाया जाता है, तब भी जारी रहता है - यानी यह एक पुनरावर्तक की सहायता करता है एक अनुक्रम में प्रत्येक आइटम के पार पारदर्शी रूप से राज्यव्यापी बनें कि उत्तराधिकारी लगातार कॉल में लौटता है।

जावास्क्रिप्ट में, एक ही अवधारणा जेनरेटर कहा जाता है।


हाल ही में रेमंड चेन भी उपज कीवर्ड पर लेखों की एक दिलचस्प श्रृंखला चलाया।

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


yield कीवर्ड आपको इट्यूमेरेट ब्लॉक पर फॉर्म में एक IEnumerable<T> बनाने की अनुमति देता है। यह इटरेटर ब्लॉक स्थगित निष्पादन का समर्थन करता है और यदि आप अवधारणा से परिचित नहीं हैं तो यह लगभग जादुई दिखाई दे सकता है। हालांकि, दिन के अंत में यह केवल कोड है जो बिना किसी अजीब चाल के निष्पादित करता है।

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

मान लें कि आपके पास एक बहुत ही सरल इटरेटर ब्लॉक है:

IEnumerable<int> IteratorBlock()
{
    Console.WriteLine("Begin");
    yield return 1;
    Console.WriteLine("After 1");
    yield return 2;
    Console.WriteLine("After 2");
    yield return 42;
    Console.WriteLine("End");
}

वास्तविक इटरेटर ब्लॉक में अक्सर स्थितियां और लूप होते हैं लेकिन जब आप शर्तों की जांच करते हैं और लूप को अनलॉक करते हैं तो वे अभी भी अन्य कोड के साथ जुड़े yield बयान के रूप में समाप्त होते हैं।

इटरेटर ब्लॉक को गिनने के लिए एक foreach लूप का उपयोग किया जाता है:

foreach (var i in IteratorBlock())
    Console.WriteLine(i);

यहां आउटपुट है (यहां कोई आश्चर्य नहीं है):

Begin
1
After 1
2
After 2
42
End

उपरोक्त बताए गए अनुसार वाक्य रचनात्मक चीनी है:

IEnumerator<int> enumerator = null;
try
{
    enumerator = IteratorBlock().GetEnumerator();
    while (enumerator.MoveNext())
    {
        var i = enumerator.Current;
        Console.WriteLine(i);
    }
}
finally
{
    enumerator?.Dispose();
}

इसे उलझाने के प्रयास में मैंने हटाए गए अवशेषों के साथ अनुक्रम आरेख को क्रेट किया है:

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

हर बार जब आप अपने इटरेटर ब्लॉक को कॉल करते हैं तो राज्य मशीन का एक नया उदाहरण बनाया जाता है। हालांकि, इटरेटर ब्लॉक में आपका कोई भी कोड एन्युमरेटर तक निष्पादित नहीं होता है। enumerator.MoveNext() पहली बार निष्पादित होता है। इस तरह निष्पादित निष्पादन कार्य करता है। यहां एक (बल्कि मूर्ख) उदाहरण है:

var evenNumbers = IteratorBlock().Where(i => i%2 == 0);

इस बिंदु पर इटेटरेटर ने निष्पादित नहीं किया है। Where क्लॉज एक नया IEnumerable<T> बनाता है जो IEnumerable<T> को IteratorBlock द्वारा IteratorBlock लेकिन यह गणना करने योग्य अभी तक गणना नहीं हुई है। यह तब होता है जब आप foreach लूप निष्पादित करते हैं:

foreach (var evenNumber in evenNumbers)
    Console.WriteLine(eventNumber);

यदि आप दो बार गणना करने के लिए गणना करते हैं तो प्रत्येक बार राज्य मशीन का एक नया उदाहरण बनाया जाता है और आपका इटरेटर ब्लॉक दो बार एक ही कोड निष्पादित करेगा।

ध्यान दें कि LINQ विधियों जैसे ToList() , ToArray() , First() , Count() इत्यादि गणना करने के लिए एक foreach लूप का उपयोग करेंगे। उदाहरण के लिए ToList() गणना के सभी तत्वों का आकलन करेगा और उन्हें एक सूची में संग्रहीत करेगा। अब आप पुनरावर्तक ब्लॉक के निष्पादन के बिना गणना के सभी तत्वों को प्राप्त करने के लिए सूची तक पहुंच सकते हैं। ToList() जैसी विधियों का उपयोग करते समय गणना के तत्वों को संग्रहीत करने के लिए गणित के कई तत्वों और स्मृति के तत्वों का उत्पादन करने के लिए CPU का उपयोग करने के बीच एक व्यापार-बंद है।







yield