Generics في C#: لماذا نحتاجها وكيف نستخدمها
الأنواع العامة (Generics) هي إحدى الإمكانيات الرئيسية للغة C#، والتي تتيح إنشاء كلاسات وطرق وواجهات بأنواع معلمة. بدلاً من كتابة نفس الكود لكل نوع بيانات، تكتبه مرة واحدة، وتحدد النوع عند الاستخدام. هذا يحسن الأداء، وسلامة الأنواع، وإعادة استخدام الكود. في هذه المقالة، سنشرح لماذا نحتاج Generics، وكيف نطبقها عملياً، ونستعرض أمثلة حقيقية من .NET.
لماذا نحتاج Generics في C#
قبل ظهور Generics في C# (الإصدار 2.0)، كان المطورون يستخدمون غالباً النوع object والمجموعات ArrayList. أدى هذا إلى مشكلتين:
- فقدان الأداء — التغليف (boxing) وإلغاء التغليف (unboxing) للأنواع ذات القيمة (int, double, struct).
- غياب التحقق من الأنواع في وقت الترجمة — يمكن وضع سلسلة نصية عن طريق الخطأ في قائمة أرقام والحصول على خطأ فقط في وقت التشغيل.
تحل Generics كلتا المشكلتين. فهي تسمح بإنشاء هياكل بيانات آمنة من حيث النوع دون تكاليف إضافية غير ضرورية. على سبيل المثال، List<int> يعمل مع int بدون تغليف، ولن يسمح المترجم بإضافة سلسلة نصية إليه.
المفاهيم الأساسية لـ Generics
الكلاسات العامة
يتم تعريف الكلاس العام (Generic class) بمعامل نوع بين قوسين زاويتين. معامل النوع هو placeholder يتم استبداله بنوع حقيقي عند إنشاء نسخة.
public class Box<T>{ private T _value;
public Box(T value) { _value = value; }
public T GetValue() => _value;}الاستخدام:
var intBox = new Box<int>(42);var stringBox = new Box<string>("Hello");
Console.WriteLine(intBox.GetValue()); // 42Console.WriteLine(stringBox.GetValue());// Helloالطرق العامة
يمكن أن يكون للطريقة معامل النوع الخاص بها، حتى لو لم يكن الكلاس عاماً:
public class Utils{ public static void Swap<T>(ref T a, ref T b) { T temp = a; a = b; b = temp; }}
// الاستخدامint x = 1, y = 2;Utils.Swap(ref x, ref y);Console.WriteLine($"{x} {y}"); // 2 1الواجهات العامة
الواجهات ذات معامل النوع تسمح بإنشاء تجريدات مرنة:
public interface IRepository<T>{ T GetById(int id); void Add(T entity);}
public class UserRepository : IRepository<User>{ public User GetById(int id) => new User(); public void Add(User user) { }}القيود (Constraints) في Generics
في بعض الأحيان، نحتاج إلى فرض قيود على الأنواع التي يمكن استخدامها. لهذا، نستخدم الكلمة المفتاحية where.
الأنواع الرئيسية للقيود
where T : class— فقط الأنواع المرجعية (string, كلاسات).where T : struct— فقط الأنواع ذات القيمة (int, DateTime, struct).where T : new()— يجب أن يحتوي النوع على مُنشئ عام بدون معاملات.where T : SomeBaseClass— يجب أن يكون النوع أو يرث من الكلاس المحدد.where T : ISomeInterface— يجب أن يقوم النوع بتطبيق الواجهة.
مثال مع قيود متعددة:
public class Repository<T> where T : class, IEntity, new(){ public T Create() { return new T(); // مسموح به بفضل new() }}Covariance و Contravariance في Generics
ابتداءً من C# 4.0، أصبح من الممكن التحكم في تباين الواجهات العامة باستخدام الكلمات المفتاحية in و out.
- التباين المشترك (Covariance) (out) — يسمح باستخ