Menu

ביטוי switch ב-C# (switch expression): תחביר, תבניות, discard ו-when

ביטוי ה-switch של C# 8 הופך ערך לתוצאה בעזרת זרועות עם תבניות, במקום case ו-break. תחביר, ה-discard _, תבניות מאפיינים, tuples ויחס, and/or/not, תנאי when, אזהרות כיסוי מלא ו-SwitchExpressionException, עם מקבילות להרצה ב-C# 7.

בדף הזה יש עורכים שאפשר להריץ - לערוך, להריץ ולראות את הפלט מיד.

ביטוי switch ממפה ערך קלט לתוצאה. הוא נוסף ב-C# 8 ומחליף את הדפוס הנפוץ של פקודת switch שבה כל case רק מחזיר או משים משהו:

// C# 8 and later
string StatusText(int code) => code switch
{
    200 => "OK",
    301 or 302 => "Redirect",   // 'or' needs C# 9
    404 => "Not Found",
    >= 500 => "Server Error",   // relational patterns need C# 9
    _ => "Unknown"
};

קראו אותו מלמעלה למטה: הערך שלפני switch נבדק מול כל זרוע לפי הסדר, והזרוע הראשונה שהתבנית שלה מתאימה מספקת את התוצאה. אין case, אין break ואין מילת מפתח default; _ (תבנית ה-discard) מתאימה לכל דבר ומשמשת כברירת מחדל.

ב-C# 7, אותו מיפוי הוא פקודת switch במתודה קטנה, עם return בכל case:

פלט:

200 OK
302 Redirect
404 Not Found
503 Server Error
418 Unknown

צורת הביטוי קצרה יותר בעיקר כי כל זרוע היא שורה אחת וגוף המתודה נעלם. היא גם ביטוי, כך שהיא יכולה לשבת בכל מקום שבו יכול לשבת ערך: ארגומנט, חור אינטרפולציה, return, מאתחל שדה.

כללי התחביר

var result = input switch
{
    pattern1 => value1,
    pattern2 when condition => value2,
    _ => fallback
};
  • הקלט בא ראשון, ואחריו switch. זה ההפך מצורת הפקודה.
  • כל זרוע היא pattern => expression. הצד הימני חייב להיות ביטוי יחיד; ללוגיקה נוספת, קראו למתודה.
  • הזרועות מופרדות בפסיקים. מותר פסיק אחרי הזרוע האחרונה.
  • כל המבנה הוא ביטוי, כך שפקודה שמשתמשת בו מסתיימת ב-; אחרי הסוגר המסולסל הסוגר.
  • כל הזרועות חייבות לייצר ערכים מטיפוס משותף. var x = n switch { 0 => "zero", _ => 0 }; לא מתקמפל (CS8506, אין טיפוס מתאים ביותר). מאז C# 9 טיפוס יעד מוצהר פותר את זה: object x = n switch { 0 => "zero", _ => 0 }; מתקמפל.
  • כל זרוע חייבת לייצר ערך, כך שזרוע לא יכולה לקרוא למתודת void כמו Console.WriteLine, וביטוי switch לא יכול לעמוד לבדו כפקודה (CS0201). לפעולות, השתמשו בפקודת switch.
  • זרוע יכולה לזרוק חריגה במקום לייצר ערך: _ => throw new ArgumentOutOfRangeException(nameof(input)).
  • הקומפיילר דוחה זרוע שלעולם לא ניתן להגיע אליה כי זרוע קודמת כבר מכסה אותה, עם שגיאה CS8510, The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. שימו זרועות ספציפיות לפני כלליות.

תבניות יחס ותבניות לוגיות (C# 9)

C# 9 הוסיפה את <, <=, >, >= כתבניות, ואת המחברים and, or ו-not. בעזרתם טבלאות טווחים נקראות כמו מפרט:

// C# 9 and later
string Grade(int score) => score switch
{
    < 0 or > 100 => throw new ArgumentOutOfRangeException(nameof(score)),
    >= 90 => "A",
    >= 80 => "B",
    >= 70 => "C",
    >= 60 => "D",
    _ => "F"
};

string AgeGroup(int age) => age switch
{
    < 13 => "child",
    >= 13 and <= 19 => "teen",
    _ => "adult"
};

bool IsLetter(char c) => c is >= 'a' and <= 'z' or >= 'A' and <= 'Z';

מכיוון שהזרועות נבדקות לפי הסדר, >= 80 רואה רק ציונים מתחת ל-90. תבניות יחס מקבלות רק קבועים בצד הימני; כדי להשוות למשתנה, השתמשו בתנאי when. המקבילה ב-C# 7 לטבלת טווחים היא שרשרת if:

פלט:

95 -> A
83 -> B
71 -> C
64 -> D
12 -> F

not משמש לרוב בתור not null: x is not null, או זרוע not null => x.Name.

תבניות מאפיינים

תבנית מאפיינים מתאימה לפי האיברים של אובייקט: { Property: pattern, ... }. בשילוב עם ביטוי switch, היא מבטאת כללים עסקיים בלי שרשרת של תנאי &&:

// C# 8 and later
decimal ShippingCost(Order order) => order switch
{
    { Total: >= 100m } => 0m,                       // relational inside: C# 9
    { Country: "US", Express: true } => 15m,
    { Country: "US" } => 5m,
    { Express: true } => 30m,
    null => throw new ArgumentNullException(nameof(order)),
    _ => 12m
};

כל מאפיין שמופיע חייב להתאים כדי שהזרוע תתאים. תבנית מאפיינים ריקה { } מתאימה לכל ערך שאינו null. C# 10 הוסיפה תבניות מאפיינים מורחבות לאיברים מקוננים: { Customer.Address.Country: "US" } במקום { Customer: { Address: { Country: "US" } } }.

תבניות tuple

switch על tuple מתאים כמה ערכים בבת אחת, וזו הדרך הנקייה לבטא טבלת החלטות:

// C# 9 and later (the 'or' pattern)
string Winner(string a, string b) => (a, b) switch
{
    ("rock", "scissors") or ("scissors", "paper") or ("paper", "rock") => "player 1",
    var (x, y) when x == y => "draw",
    _ => "player 2"
};

var (x, y) מפרק את ה-tuple לשני משתנים שתנאי ה-when יכול להשוות. ב-C# 7 התחליף המקובל הוא לשלב את הערכים למפתח אחד, או להשתמש במילון של תוצאות:

פלט:

player 1
player 2
draw

תבניות טיפוס ו-when

זרוע יכולה לבדוק את טיפוס זמן הריצה ולקשור משתנה, ופסוקית when מוסיפה כל תנאי שתחביר התבניות לא יכול לבטא:

// C# 8 and later
double Area(Shape shape) => shape switch
{
    Circle c => Math.PI * c.Radius * c.Radius,
    Rectangle r when r.Width == r.Height => r.Width * r.Width,
    Rectangle r => r.Width * r.Height,
    null => throw new ArgumentNullException(nameof(shape)),
    _ => throw new NotSupportedException(shape.GetType().Name)
};

גרסת C# 7 משתמשת בתבנית הטיפוס is בשרשרת if, שקושרת את המשתנה באותו אופן:

פלט:

Circle: 12.57
Rectangle: 13.50

כיסוי מלא ו-SwitchExpressionException

ביטוי switch חייב לייצר ערך לכל קלט. כשהקומפיילר רואה קלטים שאף זרוע לא מתאימה להם, הוא מזהיר:

warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '0' is not covered.

זו אזהרה, לא שגיאה, כך שהקוד עדיין נבנה. אם ערך שלא הותאם מגיע בזמן ריצה, הביטוי זורק System.Runtime.CompilerServices.SwitchExpressionException, שההודעה שלו מסתיימת בערך שלא הותאם (Unmatched value was 3.).

ל-enums יש מקרה מיוחד. גם כשלכל איבר עם שם יש זרוע, משתנה enum יכול להחזיק כל מספר שלם ((Size)3), ולכן הקומפיילר מדווח על CS8524 עבור "ערך enum ללא שם":

// C# 8 and later
enum Size { Small, Medium, Large }

string Code(Size s) => s switch
{
    Size.Small => "S",
    Size.Medium => "M",
    Size.Large => "L",
    // warning CS8524 without the next arm
    _ => throw new ArgumentOutOfRangeException(nameof(s))
};

הוסיפו זרוע _ מפורשת שזורקת חריגה מועילה. זה מתעד את ההנחה, משתיק את האזהרה, ונותן שגיאה ברורה יותר מ-SwitchExpressionException אם ערך שגוי יופיע אי פעם.

מילון כחלופה

כשהזרועות הן זוגות טהורים של קבוע וערך, והטבלה עשויה לגדול או להגיע מקונפיגורציה, Dictionary עושה את אותה עבודה בכל גרסה של C# ואפשר לבנות אותו בזמן ריצה:

פלט:

19.00
11.50
0

מדינה לא מוכרת מקבלת שיעור של 0m. למכפלה של decimal יש מספר ספרות אחרי הנקודה ששווה לסכום של שני האופרנדים, ולכן 100m * 0.19m מדפיס 19.00 והשיעור האפס מדפיס 0.

ביטוי switch מול פקודת switch

פקודת switchביטוי switch
זמין מאזC# 1C# 8
מייצר ערךלאכן
תחביר לכל ענףcase X: ... break;X => value,
ברירת מחדלdefault:_ =>
כמה פקודות בענףכןלא, קראו למתודה
אזהרת כיסוי מלאלאכן (CS8509)
אין התאמה בזמן ריצהשום דבר לא רץSwitchExpressionException

שתי הצורות מקבלות את אותן תבניות (טיפוסים, קבועים, מאפיינים, tuples, יחס), כך שמי שלומד אחת לומד גם את השנייה. הבחירה היא עניין של צורה: ערך עובר דרך הביטוי, רצף של פעולות עובר דרך הפקודה.

שאלות נפוצות

מה זה ביטוי switch ב-C#?

ביטוי switch, שנוסף ב-C# 8, מחושב לערך: string text = code switch { 200 => "OK", 404 => "Not Found", _ => "Unknown" };. הקלט בא לפני מילת המפתח switch, כל זרוע היא pattern => result, הזרועות מופרדות בפסיקים, ואין מילות מפתח case, break או default.

מה מקרה ברירת המחדל בביטוי switch ב-C#?

תבנית ה-discard _ מתאימה לכל דבר, כך ש-_ => "Unknown" כזרוע האחרונה ממלא את התפקיד של default. בלי זרוע שתופסת הכול, הקומפיילר מזהיר (CS8509) כשקלט כלשהו לא מכוסה, וערך שלא הותאם בזמן ריצה זורק SwitchExpressionException.

איך מתאימים כמה ערכים בזרוע אחת של ביטוי switch?

השתמשו בתבנית or מ-C# 9: "sat" or "sun" => "weekend". לטווחים, שלבו תבניות יחס עם and: >= 13 and <= 19 => "teen". ב-C# 8 הייתם מפרטים את הערכים כזרועות נפרדות עם אותה תוצאה.

אפשר להשתמש ב-when בביטוי switch ב-C#?

כן. זרוע יכולה להוסיף תנאי שמירה אחרי התבנית שלה: Order o when o.Total > 100 => 0m. הזרוע מתאימה רק אם התבנית מתאימה ותנאי ה-when אמת; אחרת החישוב ממשיך לזרוע הבאה.

כדאי להשתמש בביטוי switch או בפקודת switch?

השתמשו בביטוי כשכל ענף רק מייצר ערך, כמו מיפוי של סטטוס לתווית או חישוב מחיר. השתמשו בפקודה כשענפים מריצים כמה פקודות, יש להם תופעות לוואי או שהם צריכים לצאת מלולאה. הביטוי גם נותן אזהרות כיסוי מלא שהפקודה לא נותנת.

איור של שפות התכנות ב-Coddy

ללמוד תכנות עם Coddy

להתחיל