Generics في C#: لماذا هي ضرورية وكيفية استخدامها للمبتدئين

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

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

ابدأ الدورة

ما هي Generics في C# ولماذا هي ضرورية



تخيل أنك تكتب طريقة يجب أن تعمل مع أنواع مختلفة من البيانات: الأعداد الصحيحة، السلاسل النصية، الكائنات. بدون Generics (العامة)، سيكون عليك إما تكرار الكود لكل نوع، أو استخدام object مع التحويل اللاحق للأنواع. كلا النهجين يؤديان إلى مشاكل: الأول يؤدي إلى التكرار، والثاني يؤدي إلى أخطاء وقت التشغيل.



Generics (الأنواع العامة) هي آلية في C# تسمح بإنشاء فئات، طرق، واجهات ومفوضات تعمل مع أي نوع بيانات مع الحفاظ على الكتابة الصارمة للأنواع. بدلاً من كتابة كود لنوع معين، تستخدم معامل النوع (عادةً ما يُرمز له بالحرف T) الذي يتم استبداله بالنوع الفعلي عند الاستخدام.



المزايا الرئيسية لـ Generics:



  • أمان الأنواع — يتحقق المترجم من صحة استخدام الأنواع في مرحلة الترجمة
  • الأداء — عدم وجود التغليف/فك التغليف (boxing/unboxing) للأنواع القيمية
  • إعادة استخدام الكود — فئة/طريقة واحدة تعمل مع أنواع متعددة
  • قابلية القراءة — يصبح الكود أنظف وأكثر وضوحًا


المشكلة بدون Generics: مثال باستخدام object



قبل ظهور Generics، كان المطورون غالبًا ما يستخدمون النوع object لإنشاء طرق عامة. لنلق نظرة على مثال:



public class Stack{    private object[] _items = new object[10];    private int _count = 0;        public void Push(object item) { _items[_count++] = item; }    public object Pop() { return _items[--_count]; }        // الاستخدام    Stack stack = new Stack();    stack.Push(123);           // تغليف int إلى object    stack.Push("hello");       // يمكن إضافة أي نوع    int number = (int)stack.Pop(); // فك التغليف والتحويل}


المشاكل واضحة:

  • عند الاستخراج، تحتاج إلى تحويل نوع صريح
  • يمكن إضافة قيمة من نوع آخر عن طريق الخطأ
  • بالنسبة للأنواع القيمية (int, double) يحدث التغليف/فك التغليف مما يقلل الأداء


أساسيات العمل مع Generics في C#



إنشاء فئة عامة



لنلق نظرة على نفس مثال المكدس ولكن باستخدام Generics:



public class Stack<T>{    private T[] _items = new T[10];    private int _count = 0;        public void Push(T item) { _items[_count++] = item; }    public T Pop() { return _items[--_count]; }}

// الاستخدامStack<int> intStack = new Stack<int>();intStack.Push(123);intStack.Push(456);int number = intStack.Pop(); // لا حاجة لفك التغليف، النوع معروف

Stack<string> stringStack = new Stack<string>();stringStack.Push("Hello");// stringStack.Push(123); // خطأ في الترجمة! أمان الأنواع


يتم تحديد المعامل <T> بين قوسين زاوية. عند إنشاء مثيل، تحدد النوع المحدد، ويقوم المترجم بتوليد نسخة متخصصة من الفئة لهذا النوع.



الطرق العامة



تعمل Generics ليس فقط مع الفئات ولكن أيضًا مع الطرق المنفصلة:



public class Utils{    public static T Max<T>(T a, T b) where T : IComparable<T>    {        return a.CompareTo(b) > 0 ? a : b;    }}

// الاستخدامint maxInt = Utils.Max(5, 10); // 10string maxStr = Utils.Max("apple", "banana"); // "banana"


القيود (Constraints) في Generics



أحيانًا تحتاج إلى تقييد الأنواع التي يمكن استخدامها مع العامة. لهذا تستخدم القيود (constraints) مع الكلمة المفتاحية where:



// القيد: يجب أن يكون T نوعًا مرجعيًاpublic class Repository<T> where T : class{                

المدونات

توصيات الكتب