الاتحادات المميّزة في C# 15: مكسب انتظرناه عقدين وفخّ تغليف لا تراه
الاتحادات المميّزة وصلت أخيراً إلى C# 15 مع .NET 11: ما الذي تحلّه فعلاً في نظام الأنواع، وأين فخّ التغليف الذي يكلّفك أداءً مع الأنواع القيمية.

انتظر مطوّرو C# هذه الميزة قرابة عقدين. طلبٌ قديم بدأ في منتديات Roslyn، وتحوّل إلى نكتة متكرّرة في كل إصدار: «هل وصلت الاتحادات المميّزة هذه المرة؟». في سبتمبر 2026 وصل الجواب أخيراً بصيغة نهائية — فمع إصدار .NET 11 RC1 صارت C# 15 هي لغة المشاريع الافتراضية، ومعها الكلمة المفتاحية union بلا إعدادات تجريبية.
لكن بين الميزة كما تُعرَض في الإعلانات، والميزة كما تتصرّف في الذاكرة، فرق يستحق أن يُقال بصراحة قبل أن تعيد كتابة نصف نماذجك عليها. هذا المقال عن الاثنين معاً: لماذا تستحق الانتظار فعلاً، وأين الفخّ الذي تتجاهله أغلب الملخّصات.
ما المشكلة التي جاءت الاتحادات لتحلّها؟

تخيّل دالة تقرأ ملفاً. النتيجة إمّا محتوى ناجح، أو خطأ «ملف غير موجود»، أو خطأ «صلاحيات مرفوضة». كيف عبّرت عن هذا في C# حتى الأمس؟ أمامك ثلاثة خيارات كلها رديئة بدرجات:
- رمي استثناء لحالة متوقّعة تماماً — والاستثناءات مكلفة، وتُخفي المسارات المحتملة عن قارئ الكود.
- إرجاع
nullوترك المتلقّي يخمّن السبب — وهذا هو «الخطأ الذي كلّف المليار دولار» بعينه. - بناء صنف
Resultيدوي بخصائصIsSuccessوErrorوValue— يعمل، لكن لا شيء يمنعك من قراءةValueوأنت في حالة فشل، والمترجم لا يملك أي وسيلة ليحذّرك.
المشكلة الجذرية أن نظام الأنواع في C# كان يجيد قول «هذا الشيء و ذاك» (التركيب والوراثة)، ولا يجيد قول «هذا الشيء أو ذاك» بشكل مغلق يفهمه المترجم.
نظام الأنواع الذي لا يستطيع التعبير عن «واحد من هذه الاحتمالات حصراً» يترك التحقق كله على عاتق الانضباط البشري. والانضباط البشري يفشل عند الساعة الثانية صباحاً قبل الإصدار.
كيف تبدو الاتحادات في C# 15؟
الإعلان بسيط إلى حدّ مفاجئ. تعرّف الحالات كأنواع عادية، ثم تركّبها في اتحاد:
public union Pet(Cat, Dog, Bird);
ثلاثة أشياء تحدث فور هذا السطر:
- تحويلات ضمنية من كل نوع حالة إلى الاتحاد، فلا حاجة لتغليف يدوي عند الإسناد.
- لا حاجة لأب مشترك:
CatوDogوBirdلا يجمعها صنف أساس ولا واجهة. الاتحاد يركّب أنواعاً موجودة أصلاً في مجموعة مغلقة، حتى لو كانت من مكتبات مختلفة لا تملك تعديلها. - مطابقة نمطية شاملة يفرضها المترجم: حين تغطّي كل الحالات في
switch، يُترجم الكود بلا ذراعdefaultإطلاقاً.
النقطة الثالثة هي الجائزة الحقيقية. أضف حالة رابعة للاتحاد غداً، وسيتحوّل كل switch لم يعالجها إلى خطأ ترجمة في مكانه بالضبط — لا اختبار وحدة يكتشفه، ولا سجلّ إنتاج يبلّغ عنه بعد فوات الأوان.
أين يختلف هذا عن البدائل القديمة؟
| الأسلوب | فحص الشمولية | يحتاج أباً مشتركاً | تكلفة الذاكرة |
|---|---|---|---|
وراثة + switch على النوع |
لا (تحتاج default احتياطياً) |
نعم | تخصيص عادي |
| حزمة OneOf | وقت التشغيل | لا | تغليف حسب الحالة |
union في C# 15 |
وقت الترجمة | لا | حسب نوع الحالة — انظر أدناه |
الفرق الجوهري عن حزمة OneOf الشهيرة: هناك يقع اكتشاف الحالة المنسيّة وقت التشغيل، وهنا يقع على شاشة المترجم قبل أن يصل الكود إلى أي جهاز.
الفخّ الذي تتجاهله أغلب الملخّصات: التغليف
الآن الجزء الذي لا يظهر في العناوين. الاتحاد في صورته الافتراضية يُنفَّذ داخلياً كـ record struct يحمل قيمة object? واحدة. وهذا يعني بالضبط:
- إن كانت أنواع حالاتك مرجعية (أصناف عادية) — لا تغليف إطلاقاً، وكل شيء على ما يرام.
- إن كانت أنواع حالاتك قيمية (
int،bool،double، أو أيstruct) — كل إسناد يغلّف القيمة على الكومة (Heap).
الفخّ يظهر حيث لا تتوقّعه: في الأنواع التي تبدو أخفّ ما يمكن. اتحاد بسيط مثل union Outcome(int, bool) — أبسط ما يخطر على البال — هو أسوأ استخدام ممكن للميزة. في مسار حسّاس يُنفَّذ ملايين المرّات، تكون قد استبدلت بنية بلا تخصيص بنيةً تخصّص كائناً في كل مرة، وأضفت ضغطاً على جامع القمامة من حيث أردت أناقة الصياغة.
كيف تتفاداه؟
الحلّ متاح لكنه ليس مجانياً: اكتب بيدك record struct موسوماً بـ [Union] مع أعضاء HasValue وTryGetValue، فتتحكم في التخطيط الداخلي وتتجنّب التغليف. عملياً هذا يعني أن تختار بوعي:
- اتحاد تلقائي بصياغة سطر واحد — للحالات المرجعية والمسارات غير الساخنة، وهي الأغلبية الساحقة من كود التطبيقات.
- اتحاد مكتوب يدوياً — للأنواع القيمية داخل الحلقات الحرجة والمسارات عالية التردد فقط.
القاعدة العملية: لا تحوّل نوعاً قيمياً إلى اتحاد في مسار ساخن قبل أن تقيس. الميزة صُمّمت لتحسين وضوح النموذج، لا لتكون تحسيناً في الأداء — ومن يستخدمها كأنها الثانية سيتفاجأ.
ما الذي جاء معها في C# 15 و.NET 11؟
الاتحادات ليست وحدها في هذا الإصدار، وبعض ما رافقها قد يمسّك أكثر منها:
- Async في زمن التشغيل (Runtime Async): آلة الحالة الخاصة بـ
asyncصارت تُولَّد في زمن التشغيل بدل مترجم C#. النتيجة: تخصيصات أقل عند كلawait، وتتبّعات مكدّس (Stack Traces) أنظف بكثير — وهذه وحدها تستحق الترقية لمن يطارد أخطاء غير متزامنة في الإنتاج. الميزة لم تعد تحتاج تفعيلاً تجريبياً لمشاريعnet11.0. - تسلسلات هرمية مغلقة وأعضاء واجهة ساكنة غير افتراضية.
breakوcontinueبعلامات (Labels) للخروج من الحلقات المتداخلة بلا حيل.- فهارس الامتدادات ووسائط تعبيرات المجموعات.
- متطلبات عتاد أعلى: الحدّ الأدنى لمجموعات تعليمات x86/x64 وArm64 ارتفع. تحقّق من هذا إن كنت تنشر على خوادم قديمة، فهو أكثر ما قد يفاجئك عملياً في الترقية.
هل تبني عليها اليوم؟
الحالة الحالية: RC1 صدر في 8 سبتمبر 2026 برخصة Go-Live، أي بدعم رسمي للاستخدام الإنتاجي، والإصدار العام متوقّع في نوفمبر 2026. لكن مايكروسوفت تذكر صراحةً أن صياغة الاتحادات وتنفيذها قد يتغيّران قبل الإصدار النهائي، وأن بعض بنود المواصفة المقترحة لم تُنفَّذ بعد.
الترجمة العملية لهذا الكلام: جرّبها في مشروع جانبي أو في وحدة معزولة، ولا تعِد بناء نموذج المجال (Domain Model) بالكامل عليها هذا الشهر. سطر واحد يتغيّر في الصياغة قد يكلّفك مئات التعديلات إن سبقت الإصدار النهائي بشهرين.
أسئلة شائعة
هل أستبدل حزمة OneOf فوراً في مشاريعي؟
ليس فوراً. المكسب الحقيقي — فحص الشمولية وقت الترجمة — مغرٍ فعلاً، لكن الصياغة لم تستقرّ نهائياً قبل الإصدار العام في نوفمبر. الخطوة المعقولة: ابدأ بوحدة واحدة صغيرة، وأجّل الهجرة الشاملة إلى ما بعد الإصدار المستقر.
هل تُبطئ الاتحادات تطبيقي؟
فقط مع الأنواع القيمية في المسارات عالية التردد، بسبب التغليف الذي يشرحه القسم أعلاه. مع الأنواع المرجعية لا يوجد تغليف أصلاً. والقاعدة كما هي دائماً: قِس قبل أن تفترض، ولا تعيد التصميم بناءً على حدس.
هل تعمل الميزة على .NET 10 أو أقدم؟
لا بشكل كامل. الأنواع الداعمة (UnionAttribute وIUnion) موجودة في زمن تشغيل .NET 11؛ ومن يستهدف زمن تشغيل أقدم عليه تعريفها في مشروعه بنفسه — وهو التفاف يُبقي الشمولية، لكنه ليس مساراً مريحاً للاعتماد عليه في مشروع إنتاجي.
الخلاصة
وصول الاتحادات المميّزة إلى C# بعد كل هذا الانتظار مكسب حقيقي، وقيمته ليست في اختصار الأسطر بل في نقل اكتشاف الحالة المنسيّة من زمن التشغيل إلى زمن الترجمة. هذه هي النقلة التي تستحق العناوين.
وفي المقابل، الميزة ليست سحراً: التنفيذ الافتراضي يغلّف الأنواع القيمية على الكومة، ومن يستخدمها في مسار ساخن دون انتباه سيدفع الثمن في جامع القمامة لا في المترجم. استخدمها لتوضيح النموذج، واكتب التخطيط يدوياً حين يهمّ الأداء فعلاً — وانتظر الإصدار المستقر في نوفمبر قبل الهجرة الشاملة.