أنواع اتحادات C#: النقابات التمييزية قادمة أخيرًا

· 14 دقيقة قراءة

إذا كنت تكتب C# لفترة طويلة من الوقت، فمن المحتمل جدًا أن تصطدم بحائط عند محاولتك تصميم شيء يمكن أن يكون “واحدًا من عدة أشياء”. ربما كنت بحاجة إلى طريقة لإرجاع قيمة نجاح أو خطأ. ربما كنت تقوم ببناء نظام دفع يتعامل مع بطاقات الائتمان والتحويلات المصرفية والمحافظ الرقمية، ولكل منها بيانات مختلفة تمامًا. أو ربما نظرت للتو إلى F# أو Rust وفكرت، “لماذا لا يمكنني الحصول على ذلك في C#؟”

لقد انتهى الانتظار تقريبًا. النقابات التي تعاني من التمييز تتجه إلى لغة C#.

لقد كانت هذه إحدى الميزات اللغوية الأكثر طلبًا لسنوات، حيث تمتد مناقشات المجتمع إلى عام 2017 وما قبله. يعمل فريق تصميم لغة C# على اقتراح يقدم كلمة رئيسية union لتحديد التسلسلات الهرمية للأنواع المغلقة مع مطابقة الأنماط الشاملة. في هذا المنشور، أريد أن أطلعكم على ماهية النقابات التي تعاني من التمييز، وسبب أهميتها إلى هذا الحد، وكيف كنا نقوم بتزييفها حتى الآن، وكيف تبدو الصيغة المقترحة بالفعل - مع أمثلة كود حقيقية لكل منها.

ملاحظة سريعة قبل أن نتعمق: حتى كتابة هذه السطور، لا تزال ميزة الأنواع الموحدة في مرحلة الاقتراح والمعاينة. يعتمد بناء الجملة والسلوك الذي أصفه هنا على أحدث وثائق التصميم المتاحة للجمهور والمناقشات من فريق تصميم لغة C#. قد تتغير الأمور قبل الإصدار النهائي. سأكون واضحًا بشأن ما تم تأكيده مقابل ما لا يزال قيد المناقشة.

ما هي النقابات التي تعاني من التمييز؟

في جوهره، الاتحاد التمييزي (يسمى أحيانًا “الاتحاد الموسوم” أو “نوع المجموع”) هو نوع يمكنه الاحتفاظ بواحدة من مجموعة ثابتة من القيم المحتملة، حيث يمكن لكل متغير أن يحمل بيانات مختلفة. الجزء “التمييزي” يعني أن وقت التشغيل يعرف دائمًا المتغير - هناك علامة تحدده.

فكر في الأمر مثل enum، ولكن حيث يمكن لكل عضو حمل حمولته الخاصة من البيانات.

إذا كنت قد استخدمت لغات أخرى، فمن المحتمل أنك رأيت هذا المفهوم من قبل:

F# لديها نقابات تمييزية منذ اليوم الأول:

type Shape =
    | Circle of radius: float
    | Rectangle of width: float * height: float
    | Triangle of base: float * height: float

الصدأ يستخدم enum لنفس الفكرة:

enum Shape {
    Circle { radius: f64 },
    Rectangle { width: f64, height: f64 },
    Triangle { base: f64, height: f64 },
}

يحقق TypeScript شيئًا مشابهًا مع الاتحادات ذات العلامات:

type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "rectangle"; width: number; height: number }
  | { kind: "triangle"; base: number; height: number };

في كل هذه الحالات، يعرف المترجم كل المتغيرات الممكنة ويمكنه أن يفرض عليك التعامل معها جميعًا. هذه هي القوة العظمى: التحقق الشامل. إذا قمت بإضافة متغير جديد، يخبرك المترجم في كل مكان نسيت التعامل معه.

لم يكن لدى C# أبدًا طريقة من الدرجة الأولى للتعبير عن ذلك. حتى الآن.

كيف نحاكي النقابات اليوم

على مر السنين، توصل مجتمع C# إلى العديد من الحلول، ولكل منها مقايضاتها الخاصة. اسمحوا لي أن أتطرق إلى الأساليب الأكثر شيوعًا.

السجلات المجردة بالميراث

الحل البديل الأكثر اصطلاحًا في لغة C# الحديثة هو استخدام السجلات المجردة ذات الأنواع المشتقة المختومة:

public abstract record Shape
{
    public sealed record Circle(double Radius) : Shape;
    public sealed record Rectangle(double Width, double Height) : Shape;
    public sealed record Triangle(double Base, double Height) : Shape;

    private Shape() { } // Prevent external inheritance
}

وهذا يعمل بشكل جيد إلى حد معقول. تحصل على الثبات، والمساواة في القيمة، ويمكنك استخدام مطابقة الأنماط:

double Area(Shape shape) => shape switch
{
    Shape.Circle c => Math.PI * c.Radius * c.Radius,
    Shape.Rectangle r => r.Width * r.Height,
    Shape.Triangle t => 0.5 * t.Base * t.Height,
    _ => throw new InvalidOperationException("Unknown shape")
};
```ولكن هناك عيوب كبيرة. لا يعرف المترجم أن التسلسل الهرمي مغلق، لذلك تحتاج دائمًا إلى `_` تجاهل الذراع أو تحصل على تحذير. إذا أضفت متغيرًا جديدًا، فلن يخبرك المترجم بجميع الأماكن التي نسيت التعامل معها  حيث يبتلعها التجاهل بصمت. وهذا يتعارض تمامًا مع الهدف.

### المكتبة الوحيدة

هناك طريقة شائعة أخرى وهي حزمة [OneOf](https://github.com/mcintyre321/OneOf) NuGet:

```csharp
public OneOf<Success<Order>, NotFound, ValidationError> ProcessOrder(OrderRequest request)
{
    // ...
}

// Usage
var result = ProcessOrder(request);
result.Switch(
    success => Console.WriteLine($"Order {success.Value.Id} processed"),
    notFound => Console.WriteLine("Order not found"),
    error => Console.WriteLine($"Validation failed: {error.Message}")
);

يوفر OneOf فحصًا شاملاً في وقت الترجمة من خلال معلمات النوع العامة، وهو أمر رائع. ولكنها تعتمد على المطابقة الموضعية (النوع الأول، النوع الثاني، وما إلى ذلك)، وتصبح التوقيعات العامة غير عملية بسرعة، ولا تتكامل مع مطابقة أنماط اللغة. إنه اختراق ذكي، لكنه لا يزال اختراقًا.

التعداد اليدوي + نمط البيانات

يتبع بعض المطورين المسار الكلاسيكي باستخدام علامة التعداد وحاوية البيانات:

public enum PaymentType { CreditCard, BankTransfer, DigitalWallet }

public class Payment
{
    public PaymentType Type { get; init; }
    public CreditCardInfo? CreditCard { get; init; }
    public BankTransferInfo? BankTransfer { get; init; }
    public DigitalWalletInfo? DigitalWallet { get; init; }
}

هذا هش. لا شيء يمنعك من ضبط Type على CreditCard ولكن ملء خاصية BankTransfer. لا يستطيع المترجم مساعدتك، وينتهي الأمر بأخطاء وقت التشغيل وعمليات التحقق من القيمة الفارغة في كل مكان. إنه أسلوب “الكتابة الصارمة” في نمذجة الكتابة، ولا يمكن قياسه.

تشترك كل هذه الأساليب في مشكلة أساسية: **إنهم يحاربون اللغة بدلاً من العمل بها. ** لا يستطيع المترجم التفكير في مجموعة الاحتمالات المغلقة، لذلك تفقد الخاصية الأكثر قيمة للنقابات التمييزية - التحقق الشامل.

اقتراح C#: الكلمة الرئيسية union

يقدم اقتراح فريق لغة C# كلمة رئيسية مخصصة union تحدد مجموعة مغلقة من الأعضاء المحددين، كل منهم يحمل بيانات اختياريًا. إليك بناء الجملة الأساسي كما تم اقتراحه:

union Shape
{
    Circle(double Radius),
    Rectangle(double Width, double Height),
    Triangle(double Base, double Height)
}

هذا كل شيء. نظيفة وموجزة، ويمكن قراءتها على الفور. يحدد كل عضو داخل الاتحاد متغيرًا مميزًا ببياناته الخاصة. يعرف المترجم أن Shape يمكن أن يكون واحدًا فقط من هذه الأشياء الثلاثة.

تحت الغطاء، يقوم المترجم بإنشاء تسلسل هرمي للنوع المختوم - مشابه لما تكتبه يدويًا مع السجلات المجردة، ولكن مع وعي المترجم الكامل بالطبيعة المغلقة للنوع. هذا يعني أن المترجم يمكنه فرض الشمولية في مطابقة الأنماط، وهي الميزة الأساسية.

الأعضاء ذوو القيمة فقط

لا يتعين على أعضاء الاتحاد حمل البيانات. يمكنك مزج الأعضاء الحاملين للبيانات مع الأعضاء ذوي القيمة البسيطة:

union Option<T>
{
    Some(T Value),
    None
}

هذا هو النوع الكلاسيكي Option/Maybe الذي ظل المبرمجون الوظيفيون يطلبونه في لغة C# لسنوات. None لا يحمل أي بيانات — إنها مجرد علامة.

النقابات العامة

كما ترون من المثال Option<T> أعلاه، تدعم النقابات الأدوية العامة. فيما يلي مثال أكثر تعقيدًا:

union Result<T, E>
{
    Ok(T Value),
    Error(E Err)
}

يفتح هذا أسلوبًا كاملاً لمعالجة الأخطاء لا يعتمد على الاستثناءات لحالات الفشل المتوقعة - وهو أمر كان ممارسة قياسية في Rust واللغات الوظيفية لسنوات.

النقابات مع الأساليب

يسمح الاقتراح أيضًا للنقابات بالحصول على أساليب وخصائص محسوبة وتنفيذ واجهات، تمامًا مثل أي نوع آخر:```csharp union Shape { Circle(double Radius), Rectangle(double Width, double Height), Triangle(double Base, double Height);

public double Area => this switch
{
    Circle(var r) => Math.PI * r * r,
    Rectangle(var w, var h) => w * h,
    Triangle(var b, var h) => 0.5 * b * h
};

public double Perimeter => this switch
{
    Circle(var r) => 2 * Math.PI * r,
    Rectangle(var w, var h) => 2 * (w + h),
    Triangle(var b, var h) => b + h + Math.Sqrt(b * b + h * h)
};

}


لاحظ كيف أن تعبير `switch` داخل `Area` و`Perimeter` لا يحتاج إلى ذراع افتراضي. يعرف المترجم أن الاتحاد شامل - هناك ثلاثة متغيرات فقط، ويتم التعامل مع الثلاثة جميعًا. إذا قمت بإضافة متغير رابع لاحقًا، فسيقوم المترجم بوضع علامة على كل `switch` الذي لا يتعامل معه.

## تكامل مطابقة الأنماط

لقد تطورت مطابقة الأنماط في لغة C# منذ الإصدار 7.0، وتم تصميم أنواع الاتحاد لتكون مواطنًا من الدرجة الأولى في هذا النظام.

### تعبيرات التبديل الشاملة

الميزة الأكثر تأثيرًا هي فحص التبديل الشامل. مع النقابات، المترجم **يعرف** جميع الحالات المحتملة:

```csharp
string Describe(Shape shape) => shape switch
{
    Circle(var r)         => $"A circle with radius {r}",
    Rectangle(var w, var h) => $"A {w}x{h} rectangle",
    Triangle(var b, var h)  => $"A triangle with base {b} and height {h}"
};

لا يوجد ذراع تجاهل. لا _ => throw new NotImplementedException(). إذا نسيت حالة ما، فسيقوم المترجم بإصدار خطأ، وليس تحذيرًا. وهذا تحسن أساسي في السلامة.

مطابقة الأنماط المتداخلة

تتكون النقابات بشكل طبيعي من أنماط متداخلة:

union Expr
{
    Literal(double Value),
    Add(Expr Left, Expr Right),
    Multiply(Expr Left, Expr Right),
    Negate(Expr Inner)
}

double Evaluate(Expr expr) => expr switch
{
    Literal(var v)          => v,
    Add(var left, var right) => Evaluate(left) + Evaluate(right),
    Multiply(var left, var right) => Evaluate(left) * Evaluate(right),
    Negate(var inner)        => -Evaluate(inner)
};

هذا النوع من بنية البيانات العودية شائع للغاية في المترجمين والمترجمين الفوريين ومحركات القواعد والنمذجة الرياضية. اليوم في لغة C# ستحتاج إلى تسلسل هرمي عميق للفصل ونمط الزائر. مع النقابات، الكود أبسط بشكل كبير.

شروط الحراسة

مطابقة الأنماط مع النقابات تدعم حراس when تمامًا كما تتوقع:

string Classify(Shape shape) => shape switch
{
    Circle(var r) when r > 100   => "Large circle",
    Circle(var r) when r > 10    => "Medium circle",
    Circle(_)                     => "Small circle",
    Rectangle(var w, var h) when w == h => "Square",
    Rectangle _                   => "Rectangle",
    Triangle _                    => "Triangle"
};

أمثلة عملية

اسمحوا لي أن أستعرض بعض السيناريوهات الواقعية حيث تعمل أنواع الاتحادات على تحسين الكود بشكل كبير.

نمط النتيجة: استبدال الاستثناءات للأخطاء المتوقعة

أحد الأنماط الأكثر شيوعًا في تطوير التطبيقات الحديثة هو تمثيل العمليات التي يمكن أن تنجح أو تفشل دون استخدام استثناءات للتحكم في التدفق. يجب أن تكون الاستثناءات استثنائية — أشياء مثل فشل الشبكة أو حالات نفاد الذاكرة. يعد خطأ التحقق من الصحة أو نتيجة “لم يتم العثور عليه” نتيجة متوقعة، وليس استثناءً.

union Result<T, E>
{
    Ok(T Value),
    Error(E Err)
}

union OrderError
{
    NotFound(Guid OrderId),
    InsufficientStock(string ProductId, int Requested, int Available),
    PaymentDeclined(string Reason),
    ValidationFailed(IReadOnlyList<string> Errors)
}

Result<Order, OrderError> ProcessOrder(OrderRequest request)
{
    if (!Validate(request, out var errors))
        return new ValidationFailed(errors);

    var product = catalog.Find(request.ProductId);
    if (product is null)
        return new NotFound(request.OrderId);

    if (product.Stock < request.Quantity)
        return new InsufficientStock(request.ProductId, request.Quantity, product.Stock);

    var paymentResult = paymentGateway.Charge(request.Payment);
    if (!paymentResult.Success)
        return new PaymentDeclined(paymentResult.Message);

    var order = CreateOrder(request, product);
    return new Ok(order);
}

يُجبر المتصل بعد ذلك على التعامل مع كل النتائج المحتملة:

var result = ProcessOrder(request);

var response = result switch
{
    Ok(var order) => Results.Created($"/orders/{order.Id}", order),
    Error(NotFound(var id)) => Results.NotFound($"Order {id} not found"),
    Error(InsufficientStock(var pid, var req, var avail)) =>
        Results.Conflict($"Product {pid}: requested {req}, only {avail} available"),
    Error(PaymentDeclined(var reason)) =>
        Results.UnprocessableEntity($"Payment declined: {reason}"),
    Error(ValidationFailed(var errors)) =>
        Results.BadRequest(new { Errors = errors })
};

لا توجد كتل للمحاولة، ولا توجد أنواع استثناءات منسية، ولا توجد مفاجآت في وقت التشغيل. يكون كل وضع فشل مرئيًا في توقيع النوع ويتم فرضه بواسطة المترجم. يعد هذا تحسينًا هائلاً لموثوقية واجهة برمجة التطبيقات (API).

نمذجة النطاق: أنواع الدفع

فيما يلي مثال لنمذجة النطاق الواقعي — التعامل مع طرق الدفع المختلفة في نظام التجارة الإلكترونية:

union PaymentMethod
{
    CreditCard(string CardNumber, string Expiry, string Cvv),
    BankTransfer(string Iban, string Bic),
    DigitalWallet(string Provider, string Token),
    CashOnDelivery
}

decimal CalculateProcessingFee(PaymentMethod method, decimal amount) => method switch
{
    CreditCard _                         => amount * 0.029m + 0.30m,
    BankTransfer _                       => 1.50m,
    DigitalWallet(provider: "PayPal", _) => amount * 0.034m + 0.35m,
    DigitalWallet _                      => amount * 0.025m,
    CashOnDelivery                       => 4.99m
};

string FormatForReceipt(PaymentMethod method) => method switch
{
    CreditCard(var num, _, _)       => $"Credit Card ending in {num[^4..]}",
    BankTransfer(var iban, _)       => $"Bank Transfer ({iban[..4]}****)",
    DigitalWallet(var provider, _)  => $"Digital Wallet ({provider})",
    CashOnDelivery                  => "Cash on Delivery"
};

قارن هذا بالنهج الحالي حيث سيكون لديك واجهة أو فئة مجردة بخمسة تطبيقات مختلفة موزعة على خمسة ملفات، ربما مع نمط زائر في الأعلى. يحافظ النهج الموحد على تعريف البيانات والعمليات معًا، بحيث تكون قابلة للقراءة ومدققة بشكل شامل.

آلات الدولة

أجهزة الحالة موجودة في كل مكان في البرامج — معالجة الطلبات، ومحركات سير العمل، وإدارة الاتصال، وحالة واجهة المستخدم. النقابات تجعلها واضحة وآمنة:

union ConnectionState
{
    Disconnected,
    Connecting(string Host, int Port, DateTime StartedAt),
    Connected(string Host, int Port, TcpClient Client),
    Reconnecting(string Host, int Port, int Attempt, TimeSpan Delay),
    Failed(string Host, Exception Error)
}

ConnectionState HandleEvent(ConnectionState state, ConnectionEvent evt) => (state, evt) switch
{
    (Disconnected, Connect(var host, var port)) =>
        new Connecting(host, port, DateTime.UtcNow),

    (Connecting(var h, var p, _), ConnectionSucceeded(var client)) =>
        new Connected(h, p, client),

    (Connecting(var h, var p, _), ConnectionFailed(var ex)) =>
        new Reconnecting(h, p, Attempt: 1, Delay: TimeSpan.FromSeconds(1)),

    (Reconnecting(var h, var p, var attempt, _), ConnectionSucceeded(var client)) =>
        new Connected(h, p, client),

    (Reconnecting(var h, var p, var attempt, _), ConnectionFailed(var ex)) when attempt >= 5 =>
        new Failed(h, ex),

    (Reconnecting(var h, var p, var attempt, _), ConnectionFailed(_)) =>
        new Reconnecting(h, p, attempt + 1, TimeSpan.FromSeconds(Math.Pow(2, attempt))),

    (Connected(_, _, var client), Disconnect) =>
        new Disconnected,

    _ => state // Ignore events that don't apply to current state
};

تحمل كل ولاية بالضبط البيانات ذات الصلة بتلك الدولة. لا يمكنك الوصول عن طريق الخطأ إلى TcpClient عندما تكون في حالة Connecting لأنها غير موجودة في هذا المتغير. يفرض نظام الكتابة ثوابت آلة الحالة.

اعتبارات التسلسل والتشغيل المتداخلأحد الأسئلة العملية التي تطرح فورًا مع أنواع الاتحادات هو: كيف تقوم بتسلسلها؟ إذا كنت تقوم بإنشاء واجهات برمجة التطبيقات أو تخزين البيانات، فستحتاج إلى تسلسل JSON للعمل بشكل صحيح.

لقد ناقش فريق التصميم دعم System.Text.Json المدمج لأنواع الاتحادات. يتضمن النهج المتوقع إجراء تسلسل باستخدام خاصية التمييز:

{
  "$type": "Circle",
  "radius": 5.0
}
{
  "$type": "Rectangle",
  "width": 10.0,
  "height": 20.0
}

يتوافق هذا مع دعم التسلسل متعدد الأشكال الحالي المقدم في .NET 7 بسمات JsonDerivedType. من المتوقع أن تعمل النقابات مع System.Text.Json خارج الصندوق، باستخدام اسم المتغير كمميز للنوع افتراضيًا.

بالنسبة لـ Entity Framework Core، فإن النهج المحتمل هو تخزين القيم الموحدة باستخدام عمود تمييز - على غرار كيفية عمل تعيين وراثة الجدول لكل تسلسل هرمي (TPH) بالفعل. لا يزال التكامل الدقيق لـ EF Core قيد التصميم، ولكن البنية التحتية للتعامل مع التسلسلات الهرمية من النوع المغلق موجودة بالفعل.

تجدر الإشارة إلى أن التشغيل المتداخل مع لغات .NET الأخرى يجب أن يكون سلسًا، حيث سيتم تجميع النقابات إلى تسلسلات هرمية قياسية لفئة IL تحت الغطاء. كود F# الذي يستهلك اتحاد C# سوف يراه كنوع تسلسل هرمي قياسي، والعكس صحيح.

كيفية تجربتها

اعتبارًا من وقت كتابة هذا التقرير، تتوفر أنواع الاتحاد كميزة معاينة في أحدث معاينات .NET SDK. لتجربة بناء الجملة المقترح، ستحتاج إلى:

  1. قم بتثبيت أحدث إصدار من SDK لمعاينة .NET
  2. قم بتمكين إصدار لغة المعاينة في ملف مشروعك:
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <LangVersion>preview</LangVersion>
  </PropertyGroup>
</Project>

ضع في اعتبارك أن ميزات المعاينة عرضة للتغيير. قد يتطور بناء الجملة والسلوك وتشخيصات المترجم بشكل ملحوظ قبل الإصدار النهائي. لا تشحن كود الإنتاج بالاعتماد على ميزات لغة المعاينة - ولكن قم بتجربتها وقدم تعليقاتك. يقوم فريق C# بمراقبة مناقشات مستودع csharpang بشكل نشط.

إذا كنت تريد متابعة تقدم الاقتراح، فإن الأماكن الرئيسية التي يجب مراقبتها هي:

  • مستودع dotnet/csharpang لمناقشات تصميم اللغة
  • مستودع dotnet/roslyn لتقدم تنفيذ المترجم
  • مدونة .NET للإعلانات الرسمية

المقارنة مع المقاربات الموجودة

اسمحوا لي أن أقوم بإجراء مقارنة سريعة حتى تتمكن من رؤية كيفية تكديس الأساليب المختلفة:

ميزةسجلات مجردةواحد<T1,T2>التعداد + البياناتأنواع الاتحاد
فحص الشمولية❌ لا✅ نعم❌ لا✅ نعم
مطابقة الأنماط✅ نعم❌ محدودة❌ دليل✅ أصلي
إغلاق القسري للمترجم❌ لا✅ نعم❌ لا✅ نعم
البيانات لكل متغير✅ نعم✅ نعم⚠️ هشة✅ نعم
سهولة القراءة⚠️ مطول⚠️ الموضعية❌فقير✅ ممتاز
التسلسل✅ دليل⚠️ مجمع✅ دليل✅ مدمج
نموذجي⚠️ معتدل✅ منخفض⚠️ عالي✅ الحد الأدنى
لا تبعية خارجية✅ نعم❌ نوجيت✅ نعم✅ نعم

ماذا يعني هذا بالنسبة للنظام البيئي .NET

إن إدخال النقابات التمييزية سوف ينتشر عبر النظام البيئي .NET بأكمله. وإليكم ما أتوقع رؤيته:

سيتم تحسين تصميم المكتبة. ستتمكن واجهات برمجة التطبيقات التي تعرض حاليًا null للإشارة إلى “لم يتم العثور عليه” أو طرح استثناءات لفشل التحقق من الصحة، من إرجاع أنواع Result<T, E> بدلاً من ذلك. وهذا يجعل أوضاع الفشل واضحة في توقيع النوع - يمكنك معرفة الخطأ الذي يمكن أن يحدث من خلال النظر إلى توقيع الطريقة، وليس من خلال قراءة الوثائق أو التعليمات البرمجية المصدر.

أصبحت نمذجة المجال أكثر تعبيرًا. تقلصت الفجوة بين مجال المشكلة وتمثيل الكود بشكل كبير. عندما يقول خبير المجال الخاص بك “يمكن أن تكون الدفعة عبارة عن بطاقة ائتمان، أو تحويل مصرفي، أو نقدًا عند التسليم”، يمكنك تصميم ذلك مباشرةً كاتحاد بدلاً من ترجمته إلى تسلسل هرمي للميراث.

أصبحت أفكار F# متاحة لمطوري C#. لقد أعجب العديد من مطوري C# بنظام كتابة F# عن بعد ولكنهم لم يتمكنوا من اعتماد F# في مؤسساتهم. تقدم أنواع Union إحدى أقوى ميزات F# إلى C#، وهو ما يعد مكسبًا لنظام .NET البيئي بأكمله.

أخطاء أقل في وقت التشغيل. سيؤدي التحقق الشامل وحده إلى منع فئات كاملة من الأخطاء. في كل مرة تقوم فيها بإضافة متغير جديد إلى الاتحاد، سيرشدك المترجم إلى كل مكان في قاعدة التعليمات البرمجية الذي يحتاج إلى التحديث. لا مزيد من حالات switch المنسية، ولا مزيد من NotImplementedException في الفروع الافتراضية التي تظهر فقط في الإنتاج.

الخلاصة

كانت النقابات التي تعاني من التمييز على رأس قائمة أمنيات مجتمع C# منذ ما يقرب من عقد من الزمن، وذلك لسبب وجيه. إنهم يحلون فجوة أساسية في نظام الكتابة - القدرة على نمذجة البيانات التي يمكن أن تكون “أحد الأشياء العديدة” مع الأمان الذي يفرضه المترجم.

توفر الكلمة الأساسية union المقترحة بنية واضحة وموجزة تتكامل بشكل عميق مع مطابقة أنماط C#، وتعمل مع الأدوية العامة، وتدعم الأساليب والواجهات، وتتيح فحصًا شاملاً لاكتشاف الأخطاء في وقت الترجمة بدلاً من وقت التشغيل.

سواء كنت تقوم ببناء نماذج المجال، أو تصميم واجهات برمجة التطبيقات (APIs) مع أنواع أخطاء واضحة، أو تنفيذ أجهزة الحالة، أو مجرد محاولة استبدال التسلسلات الهرمية الموروثة بشيء أكثر طبيعية - فإن أنواع الاتحاد ستغير طريقة كتابتك لـ C#.

لقد كنا ننتظر وقتا طويلا لهذا. إن بناء الجملة أنيق، والتكامل مع ميزات اللغة الحالية مدروس، وسيكون التأثير العملي محسوسًا عبر النظام البيئي .NET بأكمله.

راقب إصدارات المعاينة، وقم بتجربة الميزة، وقدم ملاحظاتك إلى فريق تصميم اللغة. هذه واحدة من تلك الميزات التي، بمجرد استخدامها، سوف تتساءل كيف كنت تعيش بدونها.