الفريق العربي للبرمجةأرشيف المنتديات · 2000 – 2023
نسخة أرشيفية للقراءة فقط — التسجيل والمشاركة مغلقان، والمحتوى محفوظ كما كان.

الاضافات في سي#2 - Iterators

مغلق
بدأه هاني الأتاسي في 9 أكتوبر 2005 · 1 رد · 1,241 مشاهدة · في Microsoft Visual C#.NET
مشاركة: واتساب X فيسبوك تيليجرام
#1 صاحب الموضوع

كلنا نعرف IEnumerable و IEnumerator من أجل انشاء class يمكن معهما من استخدام foreach . بالنسبة للاصدار الجديد من سي# فالـ Iterators هي تسهيل من أجل انشاء class يدعم IEnumerable & IEnumerator بشكل تلقائي. وبشكل عام فإن ال Iterator هي أي تابع يسمح لك باستخدام foreach من غير أن تكتب كود منفصل لل IEnumerable .

دعنا نلقي نظرة على طريقة تحويل المترجم لعبارة foreach :

// Original code:
foreach (int val in col) {
	OutputValue(val);
}

// It will be converted to:
IEnumerator e = col.GetEnumerator();
try {
   int val;
   while (e.MoveNext()) {
      val = (int)e.Current;
      OutputValue(val);
   }
}
finally {
   IDisposable d = e as IDisposable;
   if (d != null) d.Dispose();
}

إذا أردنا أن ننشئ كلاس يدعم foreach في سي# الاصدار الأول فيجب أن نكتب كود للكلاس من أجل دعم الواجهة IEnumerable ، فيها فقط تابع واحد وهو GetEnumerator يقوم بارجع كائن من نوع IEnumerator . يجب أن ننشئ كلاس آخر يدعم الواجهة IEnumerator ونكتب كود من أجل كل من التوابع MoveNext و Current و Reset . العملية تحتاج إلى كتابة الكثير من الكود . مع الـ Iterators العملية أسهل ، فكل الذي عليك هو أن أن تكتب تابع يكون عبارة عن Iterator في الكلاس وداخله تكتب كود من أجل ارجاع القيم في المصفوفة أو ال collection .

لقد تم اضافة كلمة محجوزة جديدة في سي# الاصدار الثاني وهي yield ، ويتم استخدامها كالتالي:

yield return expression;
yield break;

هذه الكلمة تستخدم داخل أي تابع يرجع نوع IEnumerable او IEnumerator ، وعندما يراها المترجم يعتبر المترجم أن التابع عبارة عن iterator . و yield تستخدمم من أجل ارجاع أي قيمة تريد من الcollection. مثلا أنظر الكود التالي:

   // We are using the generic version of IEnumerable
   class MyNumCollection : IEnumerable<int>
   {
      public IEnumerator<int> GetEnumerator() {
         yield return 0;   // first value is 0
         yield return 1;   // second value is 1
         yield return 2;   // third value is 2
         yield return 3;   // forth value is 3
      }

      // This is in place for backword compatibility with non-generic IEnumerable
      IEnumerator IEnumerable.GetEnumerator() {
         return (this as IEnumerable<int>).GetEnumerator();
      }
   }

   class Program 
   {
      static void Main(string[] args)
      {
         MyNumCollection col = new MyNumCollection();
         foreach (int val in col)
            Console.WriteLine(val);
      }
   }

لاحظ كيف استخدمنا yield من داخل GetEnumerator ، نخبر المترجم هنا أننا نريد ارجاع القيمة 0 اولا ثم 1 ثم 2 ثم 3 .. وعند تنفيذ حلقة foreach فالنتيجة سوف نطبع القيمة 0 و 1 و2 و3 على الشاشة . المترجم وراء الكواليس يقوم بانشاء كلاس يدعم الواجهة IEnumerator . يمكن رؤية هذا الكلاس عن طريق .NET Reflector . وأنصح برؤيته لمعرفة كيف يتعامل المترجم مع ال Iterators .

طبعا يمكن كتابة أي نوع كود داخل ال GetEnumerator وتستخدم yield في أي مكان ، وعند التنفيذ المترجم يحتفظ بالحالة الأخيرة لyeild . مثلا أنظر الكود التالي:

   // Generate 10 random numbers
   class RandomGenerator : IEnumerable<int>
   {
      public IEnumerator<int> GetEnumerator() {
         Random rnd = new Random();         
         for (int i = 0; i < 10; i++)
            yield return rnd.Next(1, 1001);
      }

      IEnumerator IEnumerable.GetEnumerator() {
         return (this as IEnumerable<int>).GetEnumerator();
      }
   }

   class Program 
   {
      static void Main(string[] args)
      {
         RandomGenerator col = new RandomGenerator();
         foreach (int val in col)
            Console.WriteLine(val);
      }
   }

الأمر الجميل في الكود السابق هو استخدام yield داخل حلقة .

المثال التالي يبين استخدام IEnumerable Iterator .. وهي عبارة عن تابع يرجع أرقام فيبوناشي والآخر يرجع الأرقام الأولية بين رقمين.

   class Program 
   {
      // Calculate Fibonacci for (n)
      static IEnumerable<int> Fibonacci(int n) {
         if (n < 0)
            throw new ArgumentOutOfRangeException("The value n must be >= 0");
         if (n >= 0)
            yield return 1;
         if (n >= 1)
            yield return 1;
         int prev1 = 1, prev2 = 1;
         for (int i = 2; i <= n; i++) {
            int result = checked(prev1 + prev2);
            yield return result;
            prev1 = prev2;
            prev2 = result;
         }
      }

      // Returns the prime numbers between (from) and (to)
      static IEnumerable<int> Primes(int from, int to) {
         if (from < 0 || to < 0)
            throw new ArgumentOutOfRangeException("from and to must be greater than 0");
         if (from > to)
            throw new ArgumentException("to must be larger than from or equal");
         if (from <= 1) from = 2;  // 0, 1 are not primes
         for (; from <= to; from++)
         {
            if (from % 2 == 0) continue; // even numbers are not primes
            bool isPrime = true;
            for (int i = 3; i <= (int)Math.Sqrt(from); i++) {
               if (from % i == 0) {
                  isPrime = false;
                  break;
               }
            }
            if (isPrime) yield return from;
         }
      }

      static void Main(string[] args)
      {
         Console.WriteLine("Fibonacci");
         foreach (int val in Fibonacci(5))
            Console.WriteLine(val);

         Console.WriteLine("Primes");
         foreach (int prime in Primes(1, 15))
            Console.WriteLine(prime);
      }
   }

يمكنك استخدام مفهوم التعاودية على مجموعة في ال Iterator . أنظر المثال التالي:

   class Program 
   {
      static IEnumerable<string> SubDirs(string root) {
         yield return root;

         foreach (string subdir in Directory.GetDirectories(root))
            foreach (string s in SubDirs(subdir))
               yield return s;
      }

      static void Main(string[] args)
      {
         foreach (string dir in SubDirs(@"c:\windows"))
            Console.WriteLine(dir);
      }
   }

نقاط هامة يجب أن نعرفها عند استخدام Iterators

- معظم الأنواع في مكتبة الدوت نيت عندما ترجع كائن في GetEnumerator فإنها تعيد struct بدلا من class . وبالتالي فإن كائن الIEnumerator سوف ينشئ على المكدس وهذا اسرع من انشاء الكائن في ال heap . لكن لسوء الحظ فإن ال Iterators ترجع class بدلا من struct .

- كتابة foreach سهل ورائع عند تعاملك مع collections التي يمكن أن تغير حجمها أثناء عملية سرد المحتويات أو التي يكون من الصعب أو مكلف إيجاد عدد المحتويات مسبقا. الثمن المدفوع عند استخدامك foreach هو أنه سوف يتم انشاء كائن في الذاكرة heap (في معظم الأحيان) وسوف يتم استدعاء تابعين هما MoveNext و Current من أجل كل عنصر.

الجميل في الأمر أنه تم اضافة توابع جديدة في معظم الأنواع في دوت نيت . وهذه التوابع تغنيك عن استخدام Iterators . مثلا Array و List في دوت نيت اضافت التوابع التالية:

public void ForEach(Action<T> action)
public List<T> FindAll(Predicate<T> match)
public void Sort(Comparision<T> comparision)
public List<U> ConvertAll<U>(Convert<T, U> converter)

التوابع السابقة تستخدم delegates تم تعريفها جديدا في دوت نيت وهي:

// من اجل تنفيذ مهمة معينة على الكائن
public delegate void Action<T>(T obj);

// من أجل فحص الكائن الممرر وتحديد إذا كان يتبع شرط معين أم لا
public delegate Boolean Predicate<T>(T obj);

// من أجل مقارنة قيمتين وتحديد العلاقة بينهما
public delegate Int32 Comparision<T>(T x, T y);

// من أجل تحويل القيمة من نوع إلى آخر
public delegate U Converter<T, U>(T from)

والأمثلة التالية توضح كيف يمكن استخدام التوابع السابقة وأيضا لاحظ استخدام anonymous methods فيها:

   class Program 
   {
      static void Main(string[] args)
      {
         List<string> names = new List<string>(
            new string[] { "Nizar", "Hani", "Monir", "Badr" });

         // Sort into: Badr, Hani, Monir, Nizar
         names.Sort(delegate(string a, string b) {
            return string.Compare(a, b);
         });

         // Output the sorted list
         names.ForEach(delegate(string name) {
            Console.WriteLine(name);
         });

         // Finds only the names that has the letter 'a'
         names = names.FindAll(delegate(string name) {
            return name.IndexOf('a') >= 0;
         });

         // Convert all the names into upper case
         names = names.ConvertAll<string>(delegate(string name) {
            return name.ToUpper();
         });

         // Output BADR, HANI, NIZAR
         names.ForEach(delegate(string name) {
            Console.WriteLine(name);
         });
      }

(انتهى)

Coding on the Cloud and for the Cloud!

My Blog

#2

للرفع :D

Coding on the Cloud and for the Cloud!

My Blog

هذا الموضوع مغلق.

مواضيع مشابهة