• 0
هاني الأتاسي

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

سؤال

Generics

القسم الأول:

تعتبر ال Generics من أشهر الخصائص التي تمت اضافتها في الاصدار الثاني من سي#. وسوف أحاول هنا شرحها بشكل مختصر وسريع.

تمكننا هذه الخاصية من أستخدام أنواع معطيات عامة غير معروفة عند انشائنا للكود، ومن ثم بعد ذلك يمكننا تخصيص هذا الكود ليعمل مع أي معطيات كانت. مثلا ArrayList في الاصدار الأول تقبل اضافة اي كائن من نوع Object ، وبما أن جميع كائنات الدوت نيت تندرج من Object بشكل مباشر أو آخر فإنه يمكن اضافة اي كائن إلى الArrayList حيث يمكن اضافة ارقام ، تواريخ ، اسماء ، إلخ .. طبعا عند استخراجك لهذه الكائنات يجب أن تسند الكائن إلى النوع الصحيح قبل استخدامه ، ولا تنسى أنه عند اضافتك أي قيمة بسيطة struct type فيجب على الruntime ان تقوم بعملية تغليف لهذه القيمة Boxing قبل استخدامها مما ينتج عنه بعض البطئ.

لمحة أولى على كود مكتوب باستخدام Generics :

    class MyList<T>
   {
       private T[] entries;
       private int count;

       public void Add(T elemment)
       { ... }

       public T this[int index]
       {
           get { ... }
           set { ... }
       }
   }

   class Program
   {
       public static void Main()
       {
           List<string> StringList = new List<string>();
           StringList.Add("Hani");
           StringList.Add("Monir");
           StringList.Add(343);        // Compiler Error, cannot cast 343 to string.

           ArrayList OldList = new ArrayList();
           OldList.Add("Name");
           OldList.Add(523);
           OldList.Add(DataTime.Now);
       }
   }

فوائد استخدام ال Generics :

1- الكفائة في اعادة استخدام الخوارزميات العامة . حيث يقوم مطور ما بكتابة خوارزمية عامة تعمل على أي نوع من أنواع المعطيات ومبرمج آخر يقوم باستخدامها مثلا على نوع معطيات DateTime .

2- مستخدم الخوارزمية يستطيع استخدامها بدون الحصول على الكود المصدري وهذا مختلف عن سي++ templates وجافا.

3- Type Safety: عند استخدام كود ال Generics يكون التعامل مع نوع معطيات واحد وليس مع نوع معطيات عام Object .

4- سرعة أداء البرنامج : عند استخدام أنواع معطيات بسيطة value type ، فلن يحصل اي من ال boxing عند استخدامك للgenerics لأنك تتعامل مباشرة مع النوع البسيط وليس مع Object

ماذا يمكن أن يكون نوع عام Generics:

أي من الأنواع التالية يمكن أن تكون عامة ذات بارامتر واحد أو أكثر: Classes, structs, interfaces, delegates, methods . بعض الأمثلة على استخدام Generics

    class ListNode<T> {
       public T _info;
       public ListNode<T> _next;
   }

   struct ComplextType<TFirst, TSecond> {
       TFirst _first;
       TSecond _second;
   }

   interface IComparable<T> {
       int CompareTo(T other);
   }

   void Swap<T>(ref T val1, ref T val2) {
       T temp = val1;
       val1 = val2; val2 = temp;
   }

الأنواع البسيطة Generics في منصة دوت نيت:

كل الcollections في دوت نيت تم اعادة بنائها لتستفيد من الGenerics . تم وضع الأنواع الجديدة تحت System.Collections.Generics وأيضا تم تغير اسمائها ولكنها تؤدي نفس الغرض:

ArrayList	<==>  List<T>
Hashtable <==>  Dictionary<TKey, TValue>
SortedList <==>  StoredDictionary<TKey, TValue>
Stack  <==>  Stack<T>
Queue  <==>  Queue<T>
(n/a)  <==>  LinkedList<T>

ايضا تم اضافة Interfaces جديدة تدعم ال generics معظم هذه الInterfaces تشابه نفسها الموجودة حاليا:

IList<T>
IDictionary<TKey, TValue>
ICollection<T>
IEnumuration<T>
IEnumerable<T>
IComparer<T>
IComparable<T>

نلاحظ أن البارامتر للGeneric كلها تبدأ بالحرف T ، هذا الأمر فقط متفق عليه بأنه يفضل بدأ هذا النوع من البارامتر بالحرف الكبير T . هذا الأمر مشابه لبدأ الInterface بالحرف الكبير I

أيضا تم اضافة العديد من التوابع ال static إلى الArray class .حيث يمكنك استخدام هذه التوابع مثلا من أجل ترتيب مصفوفة من أي نوع وهكذا . هذه التوابع هي:

AsReadOnly, BinarySearch, ConvertAll, Exists, Find, FindAll, FindIndex, FindLast, FindLastIndex, ForEach, IndexOf, LastIndexOf, Sort, TrueForAll

طبعا يفضل استخدام هذه التوابع خصوصا على الأنواع البسيطة struct types من أجل سرعتها.

انتاج الكود في الذاكرة للأنواع العامة Generics :

في السي++ عندما نستخدم ال Templates يقوم المترجم بانتاج كود جديد كليا لكل نوع نقوم باستخدامه مع ال Template . هذا قد يجعل حجم الملف التنفيذي كبير إذا أصرفنا في استخدام ال Templates . طبعا هذا الأمر قد تحسن جزئيا في دوت نيت .

آلية تنفيد كود الدوت نيت تقوم بعملية انتاج كود جديد للأنواع Generics في الذاكرة عندما نستخدمها من أجل كل نوع بسيط مختلف. مثلا إذا كان لدينا Stack<int> و Stack<byte> فكل نوع سوف يتم توليده في قسم مختلف في الذاكرة . أما بالنسبة للأنواع من نوع reference فالدوت نيت سوف تقوم بتوليد كود واحد لها في الذاكرة وتستخدم نفس هذا القسم من الذاكرة لأي نوع من نوع reference .

هذه الطريقة منطقية ، فالدوت نيت يمكن أن تشارك الكود إذا كان النوع من نوع reference لأن النوع reference في الذاكرة عبارة عن مؤشر فقط (بالعادة 32 بت). أما الأنواع البسيطة فقد تكون byte أو int (32 بت) و UInt32 أو struct أو غيرها . ولو فكرت بها أكثر لتجد أنه يوجد تعليمات آلة مختلفة من أجل كل نوع بسيط ، فعملية جمع عددين من نوع Int32 تختلف عن عملية جمع عددين من نوع UInt32 .

ويمكن اثبات هذا الموضوع بكتابة برنامج بسيط :

    class Test<T> {
       public void Print() {
           Console.WriteLine("Inside {0}", typeof(T));
           Debugger.Break();   // Watch the EIP CPU register here
       }
   }

   class Program
   {

       public static void Main()
       {
           Test<Int32> Int32Test = new Test<Int32>();
           Int32Test.Print();
           Test<UInt32> UInt32Test = new Test<UInt32>();
           UInt32Test.Print();
           Test<string> StringTest = new Test<string>();
           StringTest.Print();
           Test<object> ObjectTest = new Test<object>();
           ObjectTest.Print();
       }
   }

قم بمراقبة قيمة EIP ففي الحالة الأولى والثانية سوف يختلف : وهذا يعني أنا الكود تم انشائه في مكانين منفصلين في الذاكرة ، أما الحالة الثالثة والرابعة فسوف تجد أن EIP نفسها : وهذا يعني أن الكود متشارك عليه في الذاكرة .

القسم الثاني Constrains :

المترجم في حالة ال Generics يتأكد أن الكود المكتوب سوف يعمل في أي نوع من أنواع المعطيات المتاحة ، على سبيل المثال فإن التابع Swap يعمل فلى جميع أنواع المعطيات المتوفرة في ال CLR :

    void Swap<T>(ref T val1, ref T val2) {
       T temp = val1;
       val1 = val2; val2 = temp;

التابع Swap يعمل مع جميع أنواع المعطيات البسيطة struct والreference ، ويمكن استدعاء val1.ToString() لأن جميع الأنوع هي من النوع Object . طبعا لو حاولنا استدعاء التابع val1.Read() لأعطانا المترجم خطأ في الترجمة . لكن ماذا لو من بنائنا للخوارزمية فعلا أردنا فقط استخدام الأنواع من نوع Stream ، وبالتالي يمكننا استدعاء التابع Read .

يمكننا تحديد الأنواع التي يمكن استخدامها في ال Generics بما يسمى Constraints . في هذه الحالة التابع سوف يكون له حرية أكثر في استدعاء التوابع التي يكون مصمم لها . مثال :

static T Min<T>(T arg1, T arg2) where T : IComparable {
if (arg1.CompareTo(arg2) < 0) return arg1;
return arg2;
}

في المثال السابق قمنا بتخصيص أن الأنواع التي يمكن استخدامها مع التابع Min يجب أن تكون من النوع IComparable ، وبهذه الحالة يمكن استدعاء arg1.CompareTo .

القسم الثالث Generic Delegates

يمكننا استخدام Generics في تعريف ال Delegates . وهذه الميزة تساعد جدا فقد تسهل علينا في استخدام نفس ال Delegate مع عدة أنواع من المعطيات . وأكبر مثال على هذا هو ال EventHandler delegate . ففي السابق يجب علينا أن نعرف delegate خاص عندما ننشئ event جديد إذا كان يحتاج معلومات غير متوفرة في النوع EventArgs. الآن ليس من الداعي تعريف delegate جديد من أجل كل حالة:

 public delegate void EventHandler<T>(object sender, T e)
    where T : EventArgs;

public class MyForm {
    public event EventHandler<CursorPosEventArgs> CursorClick;
}

public class CursorPosEventArgs : EventArgs {
    public int X, Y;
}

ملخص:

ال Generics تعتبر من اكبر الاضافات في الاصدار الجديد ، وإذا استخدمت بالشكل الصحيح سوف تفيد البرنامج في الأداء وفي اعادة استخدام الكود . ففي السابق كنت أستغرق أكثر من نصف ساعة من أجل كتابة strong typed collection أما مع ال generics فيمكنك أن تصل إلى نفس النتيجة بدون أي وقت يذكر .

بشكل عام يمكنك استخدام الGenerics في هذه الحالات:

1- عند استخدامك لل Collections فهي سريعة جدا فيها

2- الخوارزميات التي يمكن أن نطبق عليها أكثر من نوع من المعطيات كخوارزميات الترتيب والبحث

3- أنواع المعطيات التي يمكن استخدام أكثر من نوع لمحتوياتها

4- العديد من الكود الحالي الذي كتبته ويستخدم Object يمكنك اعادة كتابته للاستفادة من Generics والتخصيص لنوع معطيات معين.

تم تعديل بواسطه هاني الأتاسي
0

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه

10 إجابة على هذا السؤال .

  • 0

هذه بعض المصادر الأخرى لموضوع الجنسيات!

مقدمة في Generics للكاتب محمد حسام المكنى باشمهندس

مقدمة للـ Generics الجزء الثاني للكاتب محمد حسام المكنى الباشمهندس

بالمناسبة Java تدعم Generics!

سأنتظر الدروس وخصوصا Partial Types بالتوفيق!!

0

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه
  • 0
بالمناسبة Java تدعم Generics!

سأنتظر الدروس وخصوصا Partial Types بالتوفيق!!

على حد علمي فإن جافا تدعم ال Generics ولكن يجب على مستخدم النوع أو الخوارزمية الحصول على الكود المصدري لها كما في سي++ templates ..

أما في سي# فيمكن أن تعطي المستخدم فقط المكتبة بعد ترجمتها كملف DLL

وهذا ماذكرته:

مستخدم الخوارزمية يستطيع استخدامها بدون الحصول على الكود المصدري وهذا مختلف عن سي++ templates وجافا.
0

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه
  • 0

هذا ليس ضروريا أبدا،، يمكنك إدراج الحزم فقط دون الشفرة المصدرية!

القصور في موضوع الجافا هو عدم القدرة على معرفة نوع المجموعة في (وقت التنفيذ)، باستعمال المرآة مثلا Reflection! لكنه يمكن ذلك في سي شارب!

كمثال لا يمكنك في الجافا أن تفعل هذا:

List <?> list=new List<Rectangle>();
//code

if(list instanceof List);// استعمال صحيح
if(list instanceof List<Rectangle>); استعمال خاطئ.. لكنه يمكنك ذلك في سي شارب!

الاستعمال السابق في جافا غير مسموح، لأن في (وقت التنفيذ) تفقد الفئات الجنسية وليس في (وقت الترجمة)! أما في سي شارب الإصدار الثاني فتستطيع الكشف عنها!

تم تعديل بواسطه أبومازن
0

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه
  • 0
2- مستخدم الخوارزمية يستطيع استخدامها بدون الحصول على الكود المصدري وهذا مختلف عن سي++ templates وجافا.

الجافا دعمت ال Generics قبل السي شارب التي لم تدعمها الى الان ( كنسخة رسمية)

هذه بعض المصادر الأخرى لموضوع الجنسيات!

الجنسيات (من اين اتيت بهذا التعريب) :D

تم تعديل بواسطه فيصل الردادي
0

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه
  • 0

قمت باضافة القسم الثاني والثالث والملخص في الموضوع الأساسي بالأعلى ..

0

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه
  • 0

على ما اعتقد دعم ال Generics هو حديث العهد سواء في الجافا او السي شارب.

يمكن القول انه جاء متأخرا بالنسبة لعمر الجافا (الGenerics هو جزء من النسخة الاخيرة من جافا)

مدني

0

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه
  • 0

عندما ظهرت #C قلنا أنها استبدلت تعقيد ال templates الموجود في ال ++C بكون جميع الكائنات ترجع إلى object.

والآن نعود أدراجنا. فهل سيظل التعقيد كما هو في ال++C.

لأنني جربت هذا من أجل برمجة data stucture في ++C فكان أمرا متعباً جداً.

وشكراً

0

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه
  • 0

نعم الtemplates في سي++ متعبة وخصوصا في رسائل الأخطاء ، وهذا الأمر يرجع إلى أن الtemplates في سي++ يتم تحويلها أثناء الترجمة وبالتالي يمكن عمل أشياء جدا ابداعية . مثلا قمت مرة بحل مسئلة أبراج هانوي أثناء الترجمة .

في سي# الGenerics أسهل بكثير ومن تجربتي لها في ال collections لم ألاحظ ابدا أنها أصعب من استخدام ال collections العادي.

0

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه
  • 0

شكراً لك أخي هاني.

أنا من المؤمنين بقوة الtemplates مع ال سي++ على الرغم من تعقيدها. يكفي أنها تريحك من كتابة الأكواد لكل نوع.

لكني متخوف من أن التعقيد قد يلحق بال #C لكن بما أن أغلب تراكيب البيانات مجهزة في .net فلن يكون هناك خوف. كذلك من خلال إستخدامك لها اخي هاني ولم تلاحظ صعوبة.

فسوف يزول هذا الإشكال. لأني لم أستخدم الإصدار الثاني إلى الآن.

وشكراً

0

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه
  • 0

هل ممكن من المشرفين تثبيت هذا الموضوع أو نسخه إلى الصفحة الرئيسية في الموقع ؟

0

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه
زوار
This topic is now closed to further replies.

  • يستعرض القسم حالياً   0 members

    لا يوجد أعضاء مسجلين يشاهدون هذه الصفحة .