सी # में उपज रिटर्न पुनरावृत्तियों का उपयोग करने का उद्देश्य / लाभ क्या है?


80

मेरे द्वारा yield return x;C # पद्धति के अंदर उपयोग किए जाने वाले सभी उदाहरणों को पूरी सूची को वापस करके उसी तरह से किया जा सकता है। उन मामलों में, क्या yield returnसिंटैक्स बनाम सूची का उपयोग करने में कोई लाभ या फायदा है ?

इसके अलावा, किस प्रकार के परिदृश्यों का yield returnउपयोग किया जाएगा जो आप पूरी सूची को वापस नहीं कर सकते हैं?


15
आप यह क्यों मान रहे हैं कि पहले स्थान पर "एक सूची" है? क्या होगा अगर वहाँ नहीं है?
एरिक लिपर्ट

2
@ एरिक, मुझे लगता है कि मैं क्या पूछ रहा था। जब आपके पास पहली सूची नहीं होगी। अब तक के उत्तरों में फ़ाइल धाराएँ और अनंत क्रम 2 बेहतरीन उदाहरण हैं।
कोडरडिसन

1
यदि आपके पास सूची है तो, निश्चित रूप से, इसे वापस करें; लेकिन अगर आप विधि के अंदर एक सूची बना रहे हैं और वापस लौट रहे हैं तो आप इसके बजाय पुनरावृत्तियों का उपयोग कर सकते हैं। एक समय में एक आइटम की उपज। इसके कई फायदे हैं।
justin.m.sese

2
मैंने 5 साल पहले यह सवाल पूछने के बाद निश्चित रूप से बहुत कुछ सीखा है!
कोडरडिसन

1
yieldS होने का सबसे बड़ा फायदा यह है कि आपको एक और मध्यवर्ती चर का नाम नहीं देना है।
नवाफल

जवाबों:


120

लेकिन क्या होगा अगर आप खुद एक संग्रह का निर्माण कर रहे थे?

सामान्य तौर पर, पुनरावृत्तियों का उपयोग वस्तुओं के अनुक्रम को आलसी बनाने के लिए किया जा सकता है । उदाहरण के लिए Enumerable.Rangeविधि में आंतरिक रूप से किसी प्रकार का संग्रह नहीं है। यह सिर्फ मांग पर अगला नंबर उत्पन्न करता है । राज्य मशीन का उपयोग करके इस आलसी अनुक्रम पीढ़ी के कई उपयोग हैं। उनमें से अधिकांश कार्यात्मक प्रोग्रामिंग अवधारणाओं के अंतर्गत आते हैं ।

मेरी राय में, यदि आप पुनरावृत्तियों को एक संग्रह के माध्यम से गणना करने के तरीके के रूप में देख रहे हैं (यह सिर्फ सबसे सरल उपयोग मामलों में से एक है), तो आप गलत तरीके से जा रहे हैं। जैसा कि मैंने कहा, पुनरावृत्तियाँ अनुक्रम लौटाने के लिए साधन हैं। अनुक्रम भी अनंत हो सकता है । अनंत लंबाई वाली सूची वापस करने और पहले 100 वस्तुओं का उपयोग करने का कोई तरीका नहीं होगा। यह है कभी कभी आलसी होने के लिए। एक संग्रह लौटना एक संग्रह जनरेटर (जो एक पुनरावृत्ति है) को वापस करने से काफी अलग है। यह सेब की तुलना संतरे से कर रही है।

काल्पनिक उदाहरण:

static IEnumerable<int> GetPrimeNumbers() {
   for (int num = 2; ; ++num) 
       if (IsPrime(num))
           yield return num;
}

static void Main() { 
   foreach (var i in GetPrimeNumbers()) 
       if (i < 10000)
           Console.WriteLine(i);
       else
           break;
}

यह उदाहरण प्राइम नंबर को 10000 से कम प्रिंट करता है। आप प्राइम नंबर जेनरेशन एल्गोरिदम को टच किए बिना इसे आसानी से एक मिलियन से कम नंबर प्रिंट करने के लिए बदल सकते हैं। इस उदाहरण में, आप सभी अभाज्य संख्याओं की सूची नहीं लौटा सकते क्योंकि अनुक्रम अनंत है और उपभोक्ता को यह भी पता नहीं है कि यह शुरू से कितने आइटम चाहता है।


सही। मैंने सूची बनाई है, लेकिन एक बार में एक आइटम को वापस करने में क्या अंतर है?
कोडरडिसन

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

15
@ डेनिस: स्मृति में एक रेखीय रूप से संग्रहित सूची के लिए, इसमें अंतर नहीं हो सकता है, लेकिन यदि आप उदाहरण के लिए, 10GB फ़ाइल की गणना कर रहे हैं और प्रत्येक पंक्ति को एक-एक करके संसाधित कर रहे हैं, तो इससे फर्क पड़ेगा।
mmx

1
एक उत्कृष्ट उत्तर के लिए +1 - मैं यह भी जोड़ना चाहूंगा कि उपज का कीवर्ड इट्रोमेट शब्दार्थों को उन स्रोतों पर लागू करने की अनुमति देता है जिन्हें पारंपरिक रूप से संग्रह नहीं माना जाता है - जैसे कि नेटवर्क सॉकेट, वेब सेवाएँ, या समसामयिक समस्याएं (देखें stackoverflow.com/questions/) 481714 / सीसीआर-उपज-और-वीबी-नेट )
एलबुशकिन

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

24

यहाँ ठीक जवाब बताते हैं कि एक लाभ yield returnयह है कि आपको एक सूची बनाने की आवश्यकता नहीं है ; लिस्ट महंगी हो सकती है। (इसके अलावा, थोड़ी देर के बाद, आप उन्हें भारी और असभ्य पाएंगे।)

लेकिन अगर आपके पास सूची न हो तो क्या होगा?

yield returnआपको कई तरीकों से डेटा संरचनाओं (जरूरी नहीं कि सूची) को पार करने की अनुमति देता है। उदाहरण के लिए, यदि आपका ऑब्जेक्ट ट्री है, तो आप अन्य सूचियों को बनाने या अंतर्निहित डेटा संरचना को बदलने के बिना नोड्स को पूर्व या बाद के आदेश में पीछे कर सकते हैं।

public IEnumerable<T> InOrder()
{
    foreach (T k in kids)
        foreach (T n in k.InOrder())
            yield return n;
    yield return (T) this;
}

public IEnumerable<T> PreOrder()
{
    yield return (T) this;
    foreach (T k in kids)
        foreach (T n in k.PreOrder())
            yield return n;
}

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

1
अब C # को yield!F # करने के तरीके को लागू करने की आवश्यकता है ताकि आपको सभी foreachकथनों की आवश्यकता न पड़े ।
कोडरडिसन

संयोग से, आपका उदाहरण "खतरों" में से एक को दर्शाता है yield return: यह अक्सर स्पष्ट नहीं होता है कि यह कुशल या अक्षम कोड कब निकलेगा। यद्यपि yield returnपुनरावर्ती रूप से उपयोग किया जा सकता है, इस तरह के उपयोग से गहराई से नेस्टेड एनुमेरेटर्स के प्रसंस्करण पर महत्वपूर्ण ओवरहेड लगाया जाएगा। मैनुअल राज्य प्रबंधन कोड के लिए अधिक जटिल हो सकता है, लेकिन बहुत अधिक कुशलता से चल सकता है।
सुपरकैट

17

आलसी मूल्यांकन / स्थगित निष्पादन

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

var query = File.ReadLines(@"C:\MyFile.txt")
                            .Where(l => l.Contains("search text") )
                            .Select(l => int.Parse(l.SubString(5,8))
                            .Where(i => i > 10 );

int sum=0;
foreach (int value in query) 
{
    sum += value;
}

इसका उत्तर बिल्कुल एक है, और यह तब तक नहीं है जब तक कि वे foreachलूप में नीचे नहीं आते हैं । भले ही मेरे पास तीन अलग लाइनक ऑपरेटर फ़ंक्शन हैं, फिर भी हम केवल एक बार फ़ाइल की सामग्री के माध्यम से लूप करते हैं।

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

अनंत सूचियाँ

एक अच्छे उदाहरण के लिए इस सवाल का मेरा जवाब देखें:
C # रिटायरमेंट फंक्शन रिटर्निंग एरर

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

बेहतर शब्दार्थ और चिंताओं को अलग करना

ऊपर से फ़ाइल उदाहरण का उपयोग करने के बाद, हम अब उस कोड से आसानी से अलग हो सकते हैं जो उस कोड से फ़ाइल को पढ़ता है जो उस कोड से अन-जरूरी लाइनों को फ़िल्टर करता है जो वास्तव में परिणामों को पार्स करता है। यह पहला, विशेष रूप से, बहुत पुन: प्रयोज्य है।

यह उन चीजों बहुत कठिन है कि गद्य से समझाने के लिए में से एक है की तुलना में यह है के साथ बस जो एक साधारण दृश्य 1 :

चिंताओं के प्रतिपक्षीय बनाम कार्यात्मक पृथक्करण

यदि आप छवि नहीं देख सकते हैं, तो यह एक ही कोड के दो संस्करणों को दिखाता है, विभिन्न चिंताओं के लिए पृष्ठभूमि पर प्रकाश डाला गया है। Linq कोड में सभी रंगों को अच्छी तरह से समूहीकृत किया गया है, जबकि पारंपरिक अनिवार्यता कोड में रंगों का परस्पर संबंध है। लेखक का तर्क है (और मैं सहमत हूं) कि यह परिणाम linq बनाम अनिवार्य कोड का उपयोग करने के लिए विशिष्ट है ... यह कि linq आपके कोड को व्यवस्थित करने के लिए एक बेहतर काम करता है जो वर्गों के बीच एक बेहतर प्रवाह है।


1 मेरा मानना ​​है कि यह मूल स्रोत है: https://twitter.com/mariofusco/status/57199921603939272784 । यह भी ध्यान दें कि यह कोड जावा है, लेकिन C # समान होगा।


1
आस्थगित निष्पादन शायद पुनरावृत्तियों का सबसे बड़ा लाभ है।
justin.m.chase

12

कभी-कभी आपको जिन दृश्यों को वापस करने की आवश्यकता होती है, वे मेमोरी में फिट होने के लिए बहुत बड़े होते हैं। उदाहरण के लिए, लगभग 3 महीने पहले मैंने MS SLQ डेटाबेस के बीच डेटा माइग्रेशन के लिए एक परियोजना में भाग लिया था। डेटा XML स्वरूप में निर्यात किया गया था। यील्ड रिटर्न XmlReader के साथ काफी उपयोगी साबित हुआ । इसने प्रोग्रामिंग को काफी आसान बना दिया। उदाहरण के लिए, मान लीजिए किसी फ़ाइल में 1000 ग्राहक तत्व थे - यदि आप इस फ़ाइल को केवल मेमोरी में पढ़ते हैं, तो इसके लिए सभी को एक ही समय में मेमोरी में स्टोर करना होगा, भले ही वे क्रमिक रूप से संभाले हों। इसलिए, आप एक-एक करके संग्रह को पार करने के लिए पुनरावृत्तियों का उपयोग कर सकते हैं। उस स्थिति में आपको केवल एक तत्व के लिए स्मृति खर्च करनी होगी।

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


9

खिलौना / प्रदर्शन परिदृश्यों में, बहुत अंतर नहीं है। लेकिन ऐसी स्थितियां हैं जहां पैदावार करने वाले उपयोगी होते हैं - कभी-कभी, पूरी सूची उपलब्ध नहीं होती है (जैसे स्ट्रीम), या सूची कम्प्यूटेशनल रूप से महंगी है और इसकी संपूर्णता में आवश्यकता नहीं है।


2

यदि पूरी सूची विशाल है, तो यह बहुत सी मेमोरी खा सकता है बस आसपास बैठने के लिए, जबकि पैदावार के साथ आप केवल वही खेलते हैं जो आपको चाहिए, जब आपको इसकी आवश्यकता होती है, भले ही कितने आइटम हों।



2

yield returnबिना सूची का निर्माण किए आप वस्तुओं का उपयोग कर सकते हैं। यदि आपको सूची की आवश्यकता नहीं है, लेकिन आइटम के कुछ सेट पर पुनरावृति करना चाहते हैं तो इसे लिखना आसान हो सकता है

foreach (var foo in GetSomeFoos()) {
    operate on foo
}

से

foreach (var foo in AllFoos) {
    if (some case where we do want to operate on foo) {
        operate on foo
    } else if (another case) {
        operate on foo
    }
}

आप यह सुनिश्चित करने के लिए सभी तर्क रख सकते हैं कि आप उपज रिटर्न का उपयोग करके अपने तरीके के अंदर फू पर काम करना चाहते हैं या नहीं और आप लूप को अधिक संक्षिप्त कर सकते हैं।


2

यहाँ मेरे पिछले स्वीकार किए जाते हैं एक ही सवाल का जवाब:

यील्ड कीवर्ड मान जोड़ा गया?

इटरेटर विधियों को देखने का एक और तरीका यह है कि वे एक एल्गोरिथ्म को "अंदर बाहर" मोड़ने का कठिन काम करते हैं। एक पार्सर पर विचार करें। यह एक धारा से पाठ को खींचता है, इसमें पैटर्न की तलाश करता है और सामग्री का एक उच्च-स्तरीय तार्किक विवरण उत्पन्न करता है।

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

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

लेकिन इसके बजाय मैं अपने पार्सर को एक इट्रेटर विधि के रूप में कैसे लिखूं?

IEnumerable<LanguageElement> Parse(Stream stream)
{
    // imperative code that pulls from the stream and occasionally 
    // does things like:

    yield return new BeginStatement("if");

    // and so on...
}

कॉलबैक-इंटरफ़ेस दृष्टिकोण की तुलना में लिखना कठिन नहीं होगा - बस LanguageElementकॉलबैक पद्धति को कॉल करने के बजाय मेरे आधार वर्ग से प्राप्त वस्तु वापस करें ।

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

इसका परिणाम यह होता है कि कस्टम API के दोनों किनारे ऐसे लगते हैं जैसे वे नियंत्रण में हों , और इसलिए लिखना और समझना आसान होता है।


2

उपज का उपयोग करने का मूल कारण यह है कि यह अपने आप ही एक सूची बनाता है / वापस करता है। हम आगे की पुनरावृति के लिए दी गई सूची का उपयोग कर सकते हैं।


वैचारिक रूप से सही लेकिन तकनीकी रूप से गलत है। यह IEnumerable का एक उदाहरण देता है जो बस एक पुनरावृत्ति सार है। यह पुनरावृत्ति वास्तव में अगला आइटम प्राप्त करने के लिए तर्क है और भौतिकवादी सूची नहीं है। उपयोग return yieldकरने से कोई सूची उत्पन्न नहीं होती है, यह केवल सूची में अगला आइटम उत्पन्न करता है और केवल जब (पुनरावृत्त) के लिए कहा जाता है।
सिनास्टेटिक
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.