Menu

אופרטור טרנרי ב-C# (?:): תחביר, טיפוסים, קינון ו-??

האופרטור המותנה של C#, condition ? a : b, בוחר אחד משני ערכים. איך הוא מחושב, למה שני הענפים צריכים טיפוס משותף (CS0173) וההמרה שפותרת את זה, אופרטורים טרנריים מקוננים, סוגריים באינטרפולציית מחרוזות, ומתי ?? ו-?. מבטאים את זה טוב יותר.

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

האופרטור המותנה, שנקרא בדרך כלל האופרטור הטרנרי, בוחר בין שני ערכים בביטוי אחד: condition ? valueIfTrue : valueIfFalse.

פלט:

0 items, shipping 4.99
1 item, shipping 4.99
5 items, shipping 0

התנאי חייב להיות bool. אחרי שהוא מחושב, רק אחד משני הענפים רץ; השני לעולם לא מחושב. זה הופך את האופרטור לבטוח לגישה מוגנת כמו list.Count > 0 ? list[0] : "empty", שבה חישוב של list[0] על רשימה ריקה היה זורק חריגה.

טרנרי מול if/else

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

// with if/else
static string Status(int stock)
{
    if (stock > 0)
        return "in stock";
    else
        return "sold out";
}

// with the ternary
static string Status(int stock) => stock > 0 ? "in stock" : "sold out";

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

הטרנרי לא יכול להחליף if שרק מבצע פעולות. זה לא מתקמפל:

stock > 0 ? Ship(order) : Refund(order);
// error CS0201: Only assignment, call, increment, decrement, await, and new object expressions can be used as a statement

אין גם טרנרי בלי else: גם ? וגם : תמיד נדרשים, כי לביטוי חייב להיות ערך בשני המקרים.

שני הענפים צריכים טיפוס משותף

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

var discount = isMember ? 10 : null;
// error CS0173: Type of conditional expression cannot be determined because
// there is no implicit conversion between 'int' and '<null>'

var label = inStock ? 1 : "none";
// error CS0173: ... between 'int' and 'string'

הפתרון הוא להמיר ענף אחד לטיפוס שרוצים, כך שהשני יומר אליו:

פלט:

no discount
7
3.5

points / 2.0 : 0 לא צריך המרה: 0 הוא int, שמומר באופן מרומז ל-double, כך שהביטוי הוא double.

C# 9 הוסיפה טיפוס יעד (target typing): כשהביטוי מושם לטיפוס מוצהר, הקומפיילר משתמש בטיפוס הזה. int? discount = isMember ? 10 : null; מתקמפל ב-C# 9 ומעלה. עם var אין טיפוס יעד, כך ש-var discount = isMember ? 10 : null; עדיין נכשל בכל גרסה.

קדימות וסוגריים

לאופרטור המותנה יש קדימות נמוכה מאוד, נמוכה מ-+, == ו-&&. זה נוח ב-a > b ? a : b, ומפתיע בתוך שרשור מחרוזות:

Console.WriteLine("Status: " + isActive ? "on" : "off");
// error CS0029: Cannot implicitly convert type 'string' to 'bool'

הקומפיילר קורא את התנאי כ-"Status: " + isActive, כלומר מחרוזת. סוגריים סביב הטרנרי פותרים את זה: "Status: " + (isActive ? "on" : "off").

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

פלט:

2 users online
You have 1 unread message
Status: online

אופרטורים טרנריים מקוננים

האופרטור אסוציאטיבי לימין, כך ש-a ? x : b ? y : z מקובץ כ-a ? x : (b ? y : z). שרשרת קטנה עם תנאי אחד בכל שורה קריאה:

פלט:

512 B
20 KB
5 MB

מעבר לשתיים או שלוש רמות, או כשהתנאים בודקים דברים שונים, עברו ל-if/else if או לביטוי switch. קינון בתוך הענף של האמת (a ? (b ? x : y) : z) הוא הגרסה שנעשית קשה למעקב ראשונה.

?? ו-?. מחליפים טרנריים נפוצים

הרבה טרנריים קיימים רק כדי לטפל ב-null. ל-C# יש אופרטורים קצרים יותר בשבילם:

טרנריצורה קצרה יותרמשמעות
name != null ? name : "guest"name ?? "guest"ערך חלופי כשיש null
customer != null ? customer.Email : nullcustomer?.Emailגישה לאיבר שסובלת null
c != null && c.Address != null ? c.Address.City : "unknown"c?.Address?.City ?? "unknown"שניהם יחד
value != null ? value : throw new ArgumentNullException(...)value ?? throw new ArgumentNullException(...)כישלון מהיר

פלט:

Lena from Berlin
guest from unknown
guest from unknown

ביטוי throw מותר גם כאחד הענפים של טרנרי: int age = input >= 0 ? input : throw new ArgumentOutOfRangeException(nameof(input));.

ref מותנה

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

// C# 7.2 and later
int wins = 0, losses = 0;
bool won = true;

ref int counter = ref (won ? ref wins : ref losses);
counter++;               // increments wins

שני הענפים חייבים להיות ref למשתנים מאותו טיפוס. זה נדיר בקוד אפליקציה; זה קיים בעיקר לקוד רגיש לביצועים שעובד עם structs גדולים או מערכים בלי להעתיק אותם.

שאלות נפוצות

מה זה האופרטור הטרנרי ב-C#?

זה האופרטור המותנה condition ? valueIfTrue : valueIfFalse, האופרטור היחיד ב-C# עם שלושה אופרנדים. הוא מחשב את התנאי, ואז מחשב ומחזיר בדיוק אחד משני הערכים: string label = age >= 18 ? "adult" : "minor";.

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

לא. גם החלק של ? וגם החלק של : הם חובה, כי הביטוי חייב תמיד לייצר ערך. אם רוצים לעשות משהו רק כשתנאי מתקיים, השתמשו בפקודת if. האופרטור הטרנרי גם לא יכול לעמוד לבדו כפקודה: x > 0 ? A() : B(); היא שגיאה CS0201.

למה מתקבלת השגיאה "Type of conditional expression cannot be determined"?

לשני הענפים אין טיפוס משותף, למשל var x = found ? 42 : null; (int ו-null) או found ? 1 : "none". המירו ענף אחד כך שיהיה טיפוס משותף: found ? (int?)42 : null. מאז C# 9 גם טיפוס יעד מוצהר עובד: int? x = found ? 42 : null; מתקמפל, אבל var עדיין נכשל.

איך משתמשים באופרטור הטרנרי בתוך אינטרפולציית מחרוזות?

עטפו אותו בסוגריים: $"{(count == 1 ? "item" : "items")}". בלעדיהם, ה-: נקרא כתחילת מפרט פורמט והקוד לא מתקמפל.

אפשר לקנן אופרטורים טרנריים ב-C#?

כן. האופרטור אסוציאטיבי לימין, כך ש-a ? x : b ? y : z פירושו a ? x : (b ? y : z). רמת קינון אחת שמפוזרת על שורות נפרדות נקראת היטב; שרשראות עמוקות יותר ברורות יותר כשרשרת if/else if או כביטוי switch.

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

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

להתחיל