Yield return في C#: السحر الكسول للمبتدئين

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

Yield return في C#: عندما يعمل الكود "بكسل" وهذا رائع

\n\n

تخيل أنك تكتب برنامجًا يجب أن يعالج مليون سطر من ملف سجل. تقوم بتحميل كل شيء بأمانة في قائمة List<string>، وتمررها إلى طريقة التصفية، ثم - فجأة! - يتجمد البرنامج، وتتسرب الذاكرة، ويضغط المستخدم بعصبية على Ctrl+Alt+Del.

\n\n

هل هذا مألوف؟ إذا واجهت هذا الموقف ولو مرة واحدة، فقد حان الوقت للتعرف على "السحر الكسول" في C# - وهو عامل yield return. إنه ليس مجرد سكر نحوي، بل أداة قوية يمكنها تغيير طريقة تعاملك مع المجموعات بشكل جذري.

\n\n

في هذه المقالة، سنشرح الاستخدام الصحيح لـ yield return من خلال أمثلة واقعية، ونلقي نظرة تحت الغطاء، ونكتشف متى نستخدمه ومتى نبتعد عنه. هيا بنا!

\n\n

ما هو yield return وكيف يعمل؟

\n\n

بشرح بسيط، yield return هو طريقة لإخبار المترجم: "مرحبًا، لا أريد إرجاع المجموعة بأكملها دفعة واحدة. دعني أعيد العناصر واحدًا تلو الآخر حسب الحاجة". يُسمى هذا الأسلوب الحسابات الكسولة (lazy evaluation).

\n\n

إليك مثالًا كلاسيكيًا:

\n\n
public IEnumerable<int> GetNumbers()\n{\n    yield return 1;\n    yield return 2;\n    yield return 3;\n}
\n\n

عند استدعاء هذه الطريقة، لا تحصل على مصفوفة [1, 2, 3]. بدلاً من ذلك، تحصل على مكرر (iterator) سيعيد الأرقام واحدًا تلو الآخر عندما تطلبها. كل استدعاء لـ MoveNext() يقدم تنفيذ الطريقة إلى yield return التالي.

\n\n

يشبه هذا خط التجميع: يتم إنتاج العناصر فقط عندما يحتاجها المستهلك. لا نسخ زائد، ولا قوائم ضخمة في الذاكرة - فقط عمل نقي وفعال.

\n\n

لماذا yield return ليس مجرد "إرجاع قائمة"؟

\n\n

يعتقد العديد من المبتدئين: "لماذا أحتاج yield return بينما يمكنني ببساطة جمع List وإرجاعه؟". دعنا نوضح بمثال محدد.

\n\n

تخيل أنك بحاجة إلى الحصول على أول 10 أرقام زوجية من تسلسل لا نهائي. باستخدام yield return، يتم ذلك بأناقة:

\n\n
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\n

حاول فعل الشيء نفسه باستخدام List<int> - إما أن تدخل في حلقة لا نهائية، أو ستخزن جميع الأرقام المولدة في الذاكرة. Yield return يمنحنا القدرة على العمل مع تسلسلات لا نهائية محتملة دون إشغال ذاكرة الوصول العشوائي.

\n\n

مثال عملي: قراءة ملف كبير

\n\n

لنعد إلى ملف السجل الذي يحتوي على مليون سطر. إليك كيف يكتب المبتدئون عادةً:

\n\n
public 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 ميجابايت، فستكون القائمة بنفس الحجم تقريبًا. وماذا لو كنت تحتاج فقط إلى تصفية الأسطر التي تحتوي على أخطاء؟ ستظل تقوم بتحميل كل شيء.

\n\n

إليك الاستخدام الصحيح لـ yield return:

\n\n
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);<                

المدونات

توصيات الكتب