Generics في C#: لماذا هي ضرورية وكيفية استخدامها عملياً
Generics (الأدوية العامة) هي إحدى الميزات الرئيسية للغة C#، والتي تتيح كتابة كود مرن وقابل لإعادة الاستخدام وآمن من حيث الأنواع. في هذه المقالة، سنشرح سبب الحاجة إلى الأدوية العامة، وكيف تعمل تحت الغطاء، وسنقدم أمثلة عملية لاستخدامها في المشاريع الحقيقية.
ما هي Generics ولماذا هي ضرورية؟
تخيل أنك بحاجة إلى تنفيذ دالة تقوم بتبديل قيمتين. بدون الأدوية العامة، سيتعين عليك كتابة إصدارات منفصلة لكل نوع بيانات:
// بدون Generics — تكرار الكودvoid SwapInt(ref int a, ref int b) { int temp = a; a = b; b = temp; }void SwapString(ref string a, ref string b) { string temp = a; a = b; b = temp; }// ... وهكذا لكل نوعمع Generics، يتم حل هذه المشكلة بأناقة:
// مع Generics — دالة واحدة لجميع الأنواعvoid Swap<T>(ref T a, ref T b){ T temp = a; a = b; b = temp;}
// الاستخدامint x = 1, y = 2;Swap(ref x, ref y); // T = int
string s1 = "Hello", s2 = "World";Swap(ref s1, ref s2); // T = stringالمزايا الرئيسية لـ Generics:
- أمان الأنواع — يتم اكتشاف أخطاء الأنواع في مرحلة الترجمة
- الأداء — عدم وجود التغليف/إلغاء التغليف للأنواع القيمية
- إعادة استخدام الكود — فئة/دالة واحدة تعمل مع أي نوع
- سهولة القراءة — يصبح الكود أكثر وضوحاً وإيجازاً
الفئات والهياكل العامة (Generic)
المثال الأكثر شيوعاً لاستخدام Generics هو المجموعات. بدلاً من كتابة فئة خاصة لكل نوع، نقوم بإنشاء فئة عامة:
public class MyList<T>{ private T[] _items; private int _count;
public MyList(int capacity = 4) { _items = new T[capacity]; _count = 0; }
public void Add(T item) { if (_count == _items.Length) { Array.Resize(ref _items, _items.Length * 2); } _items[_count++] = item; }
public T Get(int index) { if (index < 0 || index >= _count) throw new IndexOutOfRangeException(); return _items[index]; }
public int Count => _count;}
// الاستخدامvar numbers = new MyList<int>();numbers.Add(10);numbers.Add(20);
var names = new MyList<string>();names.Add("Alice");لاحظ: عند إنشاء مثيل، نحدد النوع المحدد بين قوسين زاويتين <T>. يقوم المترجم باستبدال هذا النوع في جميع الأماكن التي يُستخدم فيها T.
الطرق العامة (Generic) والقيود (constraints)
أحياناً نحتاج إلى فرض قيود على النوع T. على سبيل المثال، لضمان أن النوع يطبق واجهة معينة أو لديه مُنشئ بدون معلمات:
// القيد: يجب أن يطبق T واجهة IComparable<T>public T GetMax<T>(T a, T b) where T : IComparable<T>{ return a.CompareTo(b) > 0 ? a : b;}
// القيد: يجب أن يكون لـ T مُنشئ بدون معلماتpublic class Factory<T> where T : new(){ public T Create() { return new T(); }}
// القيد: يجب أن يكون T نوعاً قيمياً (struct)public struct Nullable<T> where T : struct{ private T _value; private bool _hasValue;
public Nullable(T value) { _value = value; _hasValue = true; }
public T Value => _hasValue ? _value : throw new InvalidOperationException(); public bool HasValue => _hasValue;}الأنواع الرئيسية للقيود:
where T : class— نوع مرجعيwhere T : struct— نوع قيميالمدونات
توصيات الكتب
اقرأ أيضًا