Menu
flag Ar iconالعربيةdown icon

مطابقة الأنماط في C#: is وأنماط switch وأنماط الخصائص والقوائم

كيف تعمل مطابقة الأنماط في C#: المعامل is مع أنماط النوع والثوابت، وجمل switch بأنماط case وwhen، وتعابير switch، وأنماط الخصائص والصفوف والمواضع، والأنماط العلائقية والمنطقية (and وor وnot)، وأنماط القوائم، وأي إصدار من C# أضاف كلًّا منها.

تحتوي هذه الصفحة على محررات قابلة للتشغيل - حرّر، شغّل، وشاهد النتيجة فوراً.

تختبر مطابقة الأنماط (pattern matching) قيمة مقابل شكل: "هل هي Circle؟"، "هل هي null؟"، "هل Status فيها "paid" وTotal أكبر من 100؟". وحين ينجح الاختبار يمكن للنمط أيضًا استخراج أجزاء من القيمة إلى متغيّرات جديدة. بدأت صغيرة في C# 7 مع المعامل is ونمت في كل إصدار منذ ذلك، لذا تذكر هذه الصفحة إصدار كل شكل.

نمط النوع مع is (C# 7)

قبل C# 7 كان فحص نوع واستخدامه يتطلب خطوتين: as مع فحص null، أو is مع تحويل. يفعل نمط النوع الاثنين معًا ويعطيك متغيّرًا بنوعه:

المخرجات:

card ending 4242: 49.90
transfer from PT50...: 1200
no payment
other payment: 5

تكون p is CardPayment card صحيحة فقط حين تكون p قيمة CardPayment غير null (أو صنفًا مشتقًا منه)، وعندها تحملها card بالنوع الساكن الصحيح. القيمة null لا تطابق نمط نوع أبدًا، ولهذا تنزل الدفعة null إلى فحص null.

النطاق والإسناد المؤكّد

يوجد متغيّر النمط في الكتلة المحيطة، لكن المترجم لا يسمح لك بقراءته إلا حيث يُعرف أن المطابقة نجحت. وهذا يجعل شكل "جملة الحراسة" يعمل، حيث تخرج مبكرًا وتستخدم المتغيّر بعد ذلك:

المخرجات:

19.99
42 is over 40
price must be text

تُظهر boxed is int n && n > 40 الشكل الشائع الآخر: المتغيّر قابل للاستخدام في الطرف الثاني من &&، لأن ذلك الطرف لا يُنفَّذ إلا حين نجحت المطابقة. أما في الطرف الثاني من || فلا يكون مُسندًا إليه، ويقول المترجم ذلك (CS0165).

تفكّ الأنماط التغليف أيضًا: تنجح boxed is int n مع int مغلّف وتعطيك القيمة دون تحويل قد يرمي استثناءً. وفي أنواع القيم القابلة لـ null تنجح maybe is int n بالضبط حين يكون لـ maybe قيمة.

نمط الثابت ونمط null

الثابت نمط أيضًا. x is null أكثرها استخدامًا، وتسمح C# 7 أيضًا بـ x is 0 وstatus is "paid" أو عضو تعداد:

if (customer is null) return;          // null check that ignores any == overload
if (count is 0) Console.WriteLine("empty");
if (level is LogLevel.Error) Alert();

تفضّل فرق كثيرة is null على == null لأنه لا يمكن تحويله عبر تحميل زائد لمعامل في النوع. وتضيف C# 9 الشكل المنفي، is not null.

الأنماط في جمل switch (C# 7)

يمكن لأنماط النوع نفسها أن تكون عناوين case، ويضيف بند when شرطًا. تُجرَّب الحالات من الأعلى إلى الأسفل، فرتّبها من المحدد إلى العام:

static string Describe(object o)
{
    switch (o)
    {
        case null:                           return "nothing";
        case int n when n < 0:               return "negative number";
        case int n:                          return $"number {n}";
        case string s when s.Length == 0:    return "empty text";
        case string s:                       return $"text of {s.Length} chars";
        case IEnumerable<int> list:          return "a sequence of numbers";
        default:                             return o.GetType().Name;
    }
}

Describe(-4);                 // negative number
Describe("");                 // empty text
Describe(new List<int>());    // a sequence of numbers
Describe(2.5);                // Double

يفحص المترجم الترتيب: الحالة التي لا يمكن أن تطابق أبدًا لأن حالة سابقة تغطيها هي الخطأ CS8120. ويُقيَّم default دائمًا أخيرًا، أينما ظهر. والمنطق نفسه مكتوبًا بسلاسل if/is، كما في مثال الدفع أعلاه، يعمل في كل إصدار من C# منذ 7.

تعابير switch (C# 8)

تعبير switch هو الشكل المضغوط لـ "احسب قيمة واحدة من عدة حالات". تأتي القيمة أولًا، ثم switch، ثم أذرع بالشكل pattern => result مفصولة بفواصل، مع _ لالتقاط كل شيء:

public abstract record Shape;
public record Circle(double Radius) : Shape;
public record Rectangle(double Width, double Height) : Shape;

static double Area(Shape shape) => shape switch
{
    Circle c => Math.PI * c.Radius * c.Radius,
    Rectangle { Width: var w, Height: var h } => w * h,
    _ => throw new ArgumentException("Unknown shape", nameof(shape)),
};

Area(new Rectangle(3, 4));   // 12

إن لم تطابق أي ذراع وقت التشغيل يرمي SwitchExpressionException، ويحذّر المترجم (CS8509) حين يرى أن بعض المدخلات غير معالجة. تغطي صفحة تعبير switch الصياغة وحدها؛ وبقية هذه الصفحة عن الأنماط التي يمكنك وضعها في الأذرع.

أنماط الخصائص (C# 8، وموسّعة في C# 10)

يطابق نمط الخاصية خصائص كائن مقابل أنماط متداخلة، بين أقواس معقوصة. ويجب أيضًا أن يكون الكائن غير null، فتعني { } وحدها "ليست null":

public record Address(string City, string Country);
public record Order(decimal Total, string Status, Address ShipTo, int Items);

static string Shipping(Order order) => order switch
{
    { Status: "cancelled" }                   => "no shipment",
    { ShipTo.Country: "PT", Total: >= 50m }   => "free, domestic",     // C# 10 dotted form
    { ShipTo.Country: "PT" }                  => "4.90, domestic",
    { Total: > 200m }                         => "free, international",
    _                                         => "12.00, international",
};

Shipping(new Order(60m, "paid", new Address("Porto", "PT"), 2));    // free, domestic
Shipping(new Order(260m, "paid", new Address("Lyon", "FR"), 2));    // free, international

في C# 8 و9 الشكل المتداخل إلزامي: { ShipTo: { Country: "PT" } }. وتسمح C# 10 بالاختصار بالنقاط المستخدم أعلاه. وتعمل أنماط الخصائص مع is أيضًا، وهذا يجعل الفحوص متعددة الشروط تُقرأ كوصف: if (order is { Status: "paid", Items: > 0 }).

الأنماط العلائقية والمنطقية (C# 9)

أضافت C# 9 المقارنات (< و<= و> و>=) والروابط and وor وnot، التي تحوّل النطاقات إلى أنماط:

static string Grade(int score) => score switch
{
    < 0 or > 100      => "invalid",
    >= 90             => "A",
    >= 75 and < 90    => "B",
    >= 50             => "C",
    _                 => "F",
};

Grade(82);    // B
Grade(101);   // invalid

if (input is not null and not "") { ... }
if (c is >= 'a' and <= 'z' or >= 'A' and <= 'Z') { ... }   // and binds tighter than or

المكافئ في C# 7 سلسلة if/else if أو switch مع بنود when (case int s when s >= 90:).

أنماط الصفوف والمواضع (C# 8)

يتم التفرّع على عدة قيم معًا بالتفرّع على صف:

static string Quadrant(int x, int y) => (x, y) switch
{
    (0, 0)       => "origin",
    (> 0, > 0)   => "I",
    (< 0, > 0)   => "II",
    (< 0, < 0)   => "III",
    (> 0, < 0)   => "IV",
    _            => "on an axis",
};

Quadrant(-2, 5);   // II

ويفعل النمط الموضعي الشيء نفسه لأي نوع له دالة Deconstruct، بما في ذلك السجلات: case Point(0, 0): أو p is Rectangle(var w, var h) && w == h.

أنماط القوائم (C# 11)

تطابق أنماط القوائم المصفوفات والقوائم بعناصرها. تطابق .. أي عدد من العناصر ("شريحة")، ويمكن لكل موضع عنصر أن يحمل أي نمط آخر:

static string Route(string[] args) => args switch
{
    []                         => "help",
    ["add", var item]          => $"add {item}",
    ["remove", var item, ..]   => $"remove {item}",
    [var cmd, ..]              => $"unknown command {cmd}",
};

Route(new[] { "add", "milk" });             // add milk
Route(new[] { "remove", "milk", "now" });   // remove milk

int[] nums = { 1, 2, 3, 4 };
if (nums is [1, .., var last]) Console.WriteLine(last);   // 4

أي إصدار أضاف ماذا

النمطالمثالالإصدار
نمط النوع مع متغيّرo is Circle cC# 7.0
نمط الثابتx is null، n is 0C# 7.0
نمط varx is var vC# 7.0
الأنماط في عناوين case، حراسات whencase int n when n < 0:C# 7.0
تعبير switch، المُهمَل _x switch { ... }C# 8
نمط الخاصية{ Status: "paid" }C# 8
نمط الصف(x, y) switch { (0, 0) => ... }C# 8
النمط الموضعيPoint(0, 0)C# 8
النمط العلائقي> 100، <= 0C# 9
الأنماط المنطقيةnot null، >= 1 and <= 5، 'a' or 'b'C# 9
نمط النوع المجردcase Circle:، Circle => ...C# 9
نمط الخاصية الموسّع{ ShipTo.City: "Porto" }C# 10
نمط القائمة والشريحة[first, .., last]C# 11

أخطاء شائعة

  • ترتيب الحالات من العام إلى المحدد. case Payment p: قبل case CardPayment c: تجعل الثانية غير قابلة للوصول (CS8120). المحدد أولًا.
  • توقّع أن يطابق نمط النوع null. null is string s خاطئة. عالج null بحالة خاصة بها أو بـ is null.
  • قراءة متغيّر النمط حيث قد تكون المطابقة فشلت. بعد a is int n || ... لا تكون n مُسندًا إليها (CS0165).
  • تفرّعات أنواع طويلة بدل الدوال الافتراضية. إن كان كل صنف فرعي جديد يعني تعديل switch نفسها في خمسة أماكن، فمكان السلوك التسلسل الهرمي للأصناف. تتألق الأنماط مع البيانات الآتية من الخارج (أشكال JSON والرسائل وصفوف المدخلات) ومع مجموعات الأنواع المغلقة التي تتحكم فيها.
  • نسيان التقاط كل شيء في تعبير switch. المدخل غير المطابق يرمي وقت التشغيل؛ اقرأ التحذير CS8509.

الأسئلة الشائعة

ما هي مطابقة الأنماط في C#؟

تختبر مطابقة الأنماط قيمة مقابل شكل، وحين تطابق يمكنها استخراج أجزاء منها إلى متغيّرات في الخطوة نفسها. تختبر if (shape is Circle c) النوع وتعطيك c بنوعه؛ وتختبر order is { Status: "paid", Total: > 100 } الخصائص. تظهر الأنماط في تعابير is وجمل switch وتعابير switch.

كيف أستخدم is مع متغيّر في C#؟

تتحقق if (obj is Customer c) { ... } أن obj من نوع Customer (وليست null) وتسندها إلى c، بنوع Customer، داخل if. تحلّ محل الزوج الأقدم var c = obj as Customer; if (c != null). ولا يكون المتغيّر مُسندًا إليه بشكل مؤكّد إلا حيث يُعرف أن الاختبار صحيح، فتترك if (!(obj is Customer c)) return; المتغيّر c قابلًا للاستخدام بعد if.

ما الفرق بين is null و== null في C#؟

تفحص x is null المرجع نفسه دائمًا. أما x == null فتستدعي المعامل == للنوع إن كان يحمّله تحميلًا زائدًا، وهذا يعطي عادة الإجابة نفسها لكنه غير مضمون. وللعكس اكتب x != null أو !(x is null) أو x is not null منذ C# 9.

كيف تعمل مطابقة الأنماط في switch مع when في C#؟

يمكن لعنوان case أن يحمل نمطًا مع حراسة when: case Order o when o.Total > 100:. لا تطابق الحالة إلا إن طابق النمط وكان الشرط صحيحًا، وتُجرَّب الحالات من الأعلى إلى الأسفل، فضع الأكثر تحديدًا أولًا. ويبلّغ المترجم بـ CS8120 حين لا يمكن الوصول إلى حالة أبدًا لأن حالة سابقة تغطيها أصلًا.

أي إصدار من C# أضاف أي أنماط؟

C# 7.0: أنماط النوع والثوابت وvar في is وcase، إضافة إلى when. وC# 8: تعابير switch وأنماط الخصائص والصفوف والمواضع. وC# 9: الأنماط العلائقية (> 5) والمنطقية (and وor وnot) وأنماط النوع المجردة. وC# 10: أنماط الخصائص الموسّعة ({ Address.City: "Porto" }). وC# 11: أنماط القوائم ([1, .., var last]).

Coddy programming languages illustration

تعلّم البرمجة مع Coddy

ابدأ الآن