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



7 Answers

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

Question

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

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

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




इस link का एक साधारण उदाहरण है

यहां तक ​​कि सरल उदाहरण भी हैं

public static IEnumerable<int> testYieldb()
{
    for(int i=0;i<3;i++) yield return 4;
}

ध्यान दें कि उपज वापसी विधि से वापस नहीं आ जाएगी। yield return बाद भी आप एक WriteLine रेखा डाल सकते yield return

उपर्युक्त 4 इंट्स 4,4,4,4 का आईन्यूमेरेबल बनाता है

यहां एक WriteLine साथ। सूची में 4 जोड़ देंगे, एबीसी प्रिंट करें, फिर सूची में 4 जोड़ें, फिर विधि को पूरा करें और वास्तव में विधि से वापस लौटें (विधि पूरी होने के बाद, जैसा कि बिना किसी वापसी के प्रक्रिया के साथ होता है)। लेकिन इसमें एक मूल्य होगा, int की एक IEnumerable सूची, जो इसे पूरा होने पर लौटती है।

public static IEnumerable<int> testYieldb()
{
    yield return 4;
    console.WriteLine("abc");
    yield return 4;
}

ध्यान दें कि जब आप उपज का उपयोग करते हैं, तो आप जो भी लौट रहे हैं वह फ़ंक्शन के समान प्रकार का नहीं है। यह IEnumerable सूची के भीतर एक तत्व के प्रकार का है।

आप IEnumerable रूप में विधि के रिटर्न प्रकार के साथ उपज का उपयोग करते हैं। यदि विधि का रिटर्न प्रकार int या List<int> और आप yield उपयोग yield , तो यह संकलित नहीं होगा। आप उपज के बिना IEnumerable विधि रिटर्न प्रकार का उपयोग कर सकते हैं लेकिन ऐसा लगता है कि आप IEnumerable विधि रिटर्न प्रकार के बिना उपज का उपयोग नहीं कर सकते हैं।

और इसे निष्पादित करने के लिए आपको इसे एक विशेष तरीके से कॉल करना होगा।

static void Main(string[] args)
{
    testA();
    Console.Write("try again. the above won't execute any of the function!\n");

    foreach (var x in testA()) { }


    Console.ReadLine();
}



// static List<int> testA()
static IEnumerable<int> testA()
{
    Console.WriteLine("asdfa");
    yield return 1;
    Console.WriteLine("asdf");
}



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

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

उदाहरण के लिए, हमारे पास एक ऐसा एप्लिकेशन हो सकता है जो डेटाबेस से लाखों रिकॉर्ड संसाधित करता हो। जब हम एक स्थगित निष्पादन पुल-आधारित मॉडल में 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 बनाना
हैलो बॉब

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




यह कुछ रूबी गुडनेस लाने की कोशिश कर रहा है :)
अवधारणा: यह कुछ नमूना रूबी कोड है जो सरणी के प्रत्येक तत्व को प्रिंट करता है

 rubyArray = [1,2,3,4,5,6,7,8,9,10]
    rubyArray.each{|x| 
        puts x   # do whatever with x
    }

ऐरे के प्रत्येक विधि कार्यान्वयन को कॉलर ('एक्स एक्स') पर नियंत्रित करता है जो सरणी के प्रत्येक तत्व को एक्स के रूप में अच्छी तरह से प्रस्तुत किया जाता है। कॉलर तब भी कर सकता है जो इसे एक्स के साथ करने की ज़रूरत है।

हालांकि, नेट यहाँ सभी तरह से नहीं जाता है .. सी # में आईनेमेरेबल के साथ युग्मित उपज है, जिस तरह से आप कॉलर में फोरैच लूप लिखने के लिए मजबूर कर रहे हैं जैसा कि मेंडेल की प्रतिक्रिया में देखा गया है। थोड़ा कम सुरुचिपूर्ण।

//calling code
foreach(int i in obCustomClass.Each())
{
    Console.WriteLine(i.ToString());
}

// CustomClass implementation
private int[] data = {1,2,3,4,5,6,7,8,9,10};
public IEnumerable<int> Each()
{
   for(int iLooper=0; iLooper<data.Length; ++iLooper)
        yield return data[iLooper]; 
}



yield return का उपयोग गणक के साथ किया जाता है। उपज कथन के प्रत्येक कॉल पर, कॉलर को नियंत्रण वापस कर दिया जाता है लेकिन यह सुनिश्चित करता है कि कैली का राज्य बनाए रखा जाए। इसके कारण, जब कॉलर अगले तत्व की गणना करता है, तो यह yield कथन के तुरंत बाद बॉल विधि में बयान विधि में निष्पादन जारी रखता है।

आइए इसे एक उदाहरण के साथ समझने की कोशिश करें। इस उदाहरण में, प्रत्येक पंक्ति के अनुरूप मैंने आदेश का उल्लेख किया है जिसमें निष्पादन बहता है।

static void Main(string[] args)
{
    foreach (int fib in Fibs(6))//1, 5
    {
        Console.WriteLine(fib + " ");//4, 10
    }            
}

static IEnumerable<int> Fibs(int fibCount)
{
    for (int i = 0, prevFib = 0, currFib = 1; i < fibCount; i++)//2
    {
        yield return prevFib;//3, 9
        int newFib = prevFib + currFib;//6
        prevFib = currFib;//7
        currFib = newFib;//8
    }
}

इसके अलावा, राज्य प्रत्येक गणना के लिए बनाए रखा जाता है। मान लीजिए, मेरे पास Fibs() विधि के लिए एक और कॉल है, तो इसके लिए राज्य रीसेट हो जाएगा।




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

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




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 का उपयोग करने के बीच एक व्यापार-बंद है।




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




Related



Tags

c# c#   yield