خصائص الامتداد في C#: عندما تريدها ولا تستطيع (وكيف تتجاوز ذلك)
تخيل موقفًا: أنت تعمل مع مكتبة حيث تحتوي الفئة Person على خاصية FullName. كل شيء رائع، ولكن هناك مشكلة - تحتاج إلى إضافة خاصية محسوبة AgeInDays تحسب العمر بالأيام من تاريخ الميلاد. أنت معتاد على طرق الامتداد وتفكر: "لماذا لا نصنع خصائص امتداد؟"
ثم تذهب إلى Stack Overflow، وترى سؤالًا بآلاف المشاهدات: "هل تحتوي C# على خصائص امتداد؟" - وتقرأ الإجابة بخيبة أمل: "لا، غير مدعومة رسميًا". لكن لا تغلق علامة التبويب! في هذه المقالة، لن نناقش فقط سبب عدم وجودها، بل سنجد أيضًا طرقًا أنيقة لتجاوز ذلك لا تقل أداءً.
لماذا لا تضيف C# خصائص الامتداد؟ السياق التاريخي
لنبدأ بالأهم: خصائص الامتداد ليست نزوة، بل طلب طويل الأمد من المجتمع. في عام 2016، ظهر اقتراح على GitHub، لكنه لم يُنفذ حتى الآن. لماذا؟
- التعقيد مع المترجم: على عكس الطرق، تحتوي الخصائص على أدوات getter و setter يجب ربطها بمثيل معين. توسيع هذا المنطق على مستوى بناء الجملة يتطلب تغييرات جذرية في المترجم.
- التعارض مع المفهرسات: الخصائص والمفهرسات في C# مرتبطة ارتباطًا وثيقًا. إضافة خصائص الامتداد قد تخلق غموضًا.
- أولوية الميزات الأخرى: ركز فريق C# على records و pattern matching و static abstract members (C# 11)، التي تقدم فائدة أكبر لمعظم السيناريوهات.
لكن غياب الدعم الرسمي ليس حكمًا بالإعدام. دعنا نرى كيف يحل المطورون ذوو الخبرة هذه المشكلة.
طرق الامتداد: أداتك الرئيسية (ولكن مع تفاصيل دقيقة)
الطريقة الأكثر وضوحًا هي استخدام طرق الامتداد بدلاً من الخصائص. بدلاً من person.AgeInDays، تكتب person.GetAgeInDays(). هذا يعمل، لكنه ينتهك مبدأ التوحيد: في بقية الكود، تستخدم الخصائص، وفجأة تستخدم طريقة.
public static class PersonExtensions{ public static int GetAgeInDays(this Person person) { return (DateTime.Now - person.BirthDate).Days; }}
// الاستخدام:var age = person.GetAgeInDays(); // يعمل، لكنه غير جميلالعيب: يصبح الكود أقل قابلية للقراءة، خاصة في سلاسل استدعاءات LINQ. الميزة: هذا مجاني تمامًا من حيث الأداء - استدعاء طريقة الامتداد لا يختلف عن أي طريقة ثابتة عادية.
البديل رقم 1: المصانع الثابتة والفئات المساعدة
إذا كنت بحاجة إلى "خاصية" تُحسب بناءً على البيانات، يمكنك إنشاء فئة مساعدة ثابتة:
public static class PersonHelper{ public static int AgeInDays(Person person) => (DateTime.Now - person.BirthDate).Days;}
// الاستخدام:var age = PersonHelper.AgeInDays(person);هذا أكثر صدقًا من طريقة الامتداد: من الواضح أنها دالة مساعدة. لكنها لا تزال ليست خاصية.
البديل رقم 2: السمات وتوليد الكود (Source Generators)
للسيناريوهات المعقدة (مثل إضافة خصائص إلى فئات من حزم NuGet)، يمكنك استخدام مولدات المصدر. ظهرت في .NET 5 وتسمح بتوليد الكود أثناء مرحلة التجميع. تكتب سمة، ويقوم المولد بإنشاء فئة جزئية بالخصائص المطلوبة.
[AutoExtensionProperty(typeof(Person), nameof(Person.BirthDate))]public partial class PersonExtensions{ // المولد سينشئ خاصية AgeInDays}
// الكود المُنشأ:public partial class PersonExt