C# में Yield return: जब कोड "आलसी" काम करता है और यह बढ़िया है
\n\nकल्पना करें कि आप एक प्रोग्राम लिख रहे हैं जिसे लॉग-फ़ाइल से दस लाख पंक्तियों को प्रोसेस करना है। आप ईमानदारी से सब कुछ List<string> सूची में लोड करते हैं, इसे फ़िल्टरिंग मेथड में भेजते हैं, और फिर — बैम! — प्रोग्राम हैंग हो जाता है, मेमोरी लीक हो जाती है, और उपयोगकर्ता घबराकर Ctrl+Alt+Del दबाता है।
परिचित लगता है? यदि आप कभी ऐसी स्थिति में आए हैं, तो आपको C# के "आलसी जादू" — yield return ऑपरेटर से परिचित होने का समय आ गया है। यह केवल सिंटैक्टिक शुगर नहीं है, बल्कि एक शक्तिशाली उपकरण है जो कलेक्शन के साथ काम करने के आपके तरीके को पूरी तरह से बदल सकता है।
इस लेख में हम वास्तविक उदाहरणों के साथ yield return के सही उपयोग को समझेंगे, इसके अंदर की कार्यप्रणाली देखेंगे, और पता लगाएंगे कि इसका उपयोग कब करना चाहिए और कब इससे दूर रहना बेहतर है। चलिए शुरू करते हैं!
\n\nyield return क्या है और यह कैसे काम करता है?
\n\nसरल शब्दों में समझाएं तो, yield return कंपाइलर को यह बताने का एक तरीका है: "अरे, मैं पूरी कलेक्शन एक साथ वापस नहीं करना चाहता। मैं तत्वों को एक-एक करके, आवश्यकतानुसार दूंगा।" इस दृष्टिकोण को आलसी गणना (lazy evaluation) कहा जाता है।
यहाँ एक क्लासिक उदाहरण है:
\n\npublic IEnumerable<int> GetNumbers()\n{\n yield return 1;\n yield return 2;\n yield return 3;\n}\n\nइस मेथड को कॉल करने पर, आपको [1, 2, 3] ऐरे नहीं मिलता। इसके बजाय, आपको एक इटरेटर मिलता है जो आपके पूछने पर एक-एक करके संख्याएँ देगा। MoveNext() का प्रत्येक कॉल मेथड के निष्पादन को अगले yield return तक आगे बढ़ाता है।
यह एक कन्वेयर बेल्ट की तरह है: तत्व केवल तब उत्पन्न होते हैं जब उपभोक्ता को उनकी आवश्यकता होती है। कोई अनावश्यक कॉपी नहीं, मेमोरी में कोई विशाल सूचियाँ नहीं — केवल स्वच्छ, कुशल कार्य।
\n\nyield return केवल "सूची लौटाना" क्यों नहीं है?
\n\nकई शुरुआती सोचते हैं: "मुझे yield return की क्या ज़रूरत है, जब मैं बस एक List बनाकर लौटा सकता हूँ?" आइए एक विशिष्ट उदाहरण से समझते हैं।
कल्पना करें कि आपको एक अनंत अनुक्रम से पहले 10 सम संख्याएँ प्राप्त करनी हैं। yield return के साथ यह सुंदरता से किया जाता है:
public IEnumerable<int> GetEvenNumbers()\n{\n int i = 0;\n while (true)\n {\n yield return i;\n i += 2;\n }\n}\n\n// उपयोग\nvar firstTenEven = GetEvenNumbers().Take(10).ToList();\n\nList<int> के साथ भी ऐसा करने का प्रयास करें — और आप या तो अनंत लूप में फंस जाएंगे, या सभी उत्पन्न संख्याओं को मेमोरी में संग्रहीत करेंगे। Yield return हमें संभावित रूप से अनंत अनुक्रमों के साथ काम करने की क्षमता देता है, बिना रैम को भरे।
व्यावहारिक उदाहरण: बड़ी फ़ाइल पढ़ना
\n\nअपने दस लाख पंक्तियों वाले लॉग-फ़ाइल पर वापस आते हैं। यहाँ बताया गया है कि शुरुआती आमतौर पर कैसे लिखते हैं:
\n\npublic List<string> ReadLogLines(string path)\n{\n var lines = new List<string>();\n using (var reader = new StreamReader(path))\n {\n string line;\n while ((line = reader.ReadLine()) != null)\n {\n lines.Add(line);\n }\n }\n return lines;\n}\n\nसमस्या: पूरी फ़ाइल मेमोरी में लोड हो जाती है। यदि फ़ाइल 500 MB है, तो सूची भी लगभग उतनी ही होगी। और यदि आपको केवल त्रुटियों वाली पंक्तियों को फ़िल्टर करना है? फिर भी आप सब कुछ लोड करेंगे।
\n\nअब yield return का सही उपयोग:
public IEnumerable<string> ReadLogLines(string path)\n{\n using (var reader = new StreamReader(path))\n {\n string line;\n while ((line = reader.ReadLine()) != null)\n {\n yield return line;\n }\n }\n}\n\n// पूरी फ़ाइल लोड किए बिना फ़िल्टरिंग\nvar errorLines = ReadLogLines("log.txt")\n .Where(line => line.Contains("ERROR"))\n .Take(100);