משתנה הוא מקום אחסון עם שם וטיפוס קבוע. ב-C# מצהירים עליו עם טיפוס ושם, ואפשר גם לתת לו ערך באותה פקודה. הטיפוס לעולם לא משתנה אחר כך: משתנה int מחזיק מספרים שלמים כל עוד הוא קיים.
פלט:
Priya bought 5 items for 20.25
10
השמה של ערך מהטיפוס הלא נכון היא שגיאת קומפילציה: quantity = "five"; נכשלת עם CS0029 (cannot implicitly convert type 'string' to 'int'). המרה בין טיפוסים היא מפורשת; ראו המרת טיפוסים.
var: לתת לקומפיילר להסיק את הטיפוס
var מצהיר על משתנה מקומי שהטיפוס שלו מוסק מהמאתחל שלו. זו לא הקלדה דינמית: הטיפוס נקבע בזמן קומפילציה, בדיוק כאילו כתבתם אותו במפורש.
פלט:
String
Int32
Decimal
Double
List`1
ל-var יש שלושה כללים. הוא עובד רק עבור משתנים מקומיים (לא שדות ולא פרמטרים). המשתנה חייב להיות מאותחל בהצהרה: var x; היא שגיאה CS0818, "Implicitly-typed variables must be initialized". ולמאתחל חייב להיות טיפוס: var nothing = null; לא מתקמפל, כי ל-null לבדו אין טיפוס.
מתי להשתמש בו זה עניין של סגנון הצוות. ההנחיה המקובלת היא להשתמש ב-var כשהטיפוס ברור מהצד הימני, ולכתוב את הטיפוס כשהוא לא ברור:
var orders = new List<Order>(); // obvious: var is fine
var reader = new StreamReader(path); // obvious
decimal discount = CalculateDiscount(); // not obvious: name the type
int count = items.Count(); // makes the intent clear
מלכודת עדינה: var total = 0; הופך את total ל-int, כך שהוספת 2.5 אליו מאוחר יותר נכשלת, ו-var price = 10; הוא int, לא decimal. כשהטיפוס של הליטרל אינו הטיפוס שרוצים, כתבו את הטיפוס או הוסיפו סיומת (10m, 0.0).
טווח (scope)
משתנה מקומי קיים מההצהרה שלו ועד סוף הבלוק { } שבו הוצהר. משתנים שהוצהרו בכותרת של לולאה או בגוף של if לא נראים אחריהם.
פלט:
high
240
בניגוד ל-C ול-C++, C# לא מאפשרת לבלוק פנימי להצהיר על משתנה מקומי עם אותו שם כמו משתנה בבלוק עוטף (שגיאה CS0136): לשם חייבת להיות משמעות אחת לאורך כל המתודה. שני בלוקים אחים יכולים להשתמש שוב באותו שם, כמו שתי לולאות for נפרדות שכל אחת מצהירה על i.
השמה ודאית
למשתנים מקומיים אין ערך ברירת מחדל. הקומפיילר עוקב אחרי כל מסלול במתודה ומסרב לקמפל אם ייתכן שמשתנה ייקרא לפני שהושם לו ערך:
int discount;
if (isMember)
{
discount = 10;
}
Console.WriteLine(discount);
// error CS0165: Use of unassigned local variable 'discount'
תקנו את זה באתחול בהצהרה (int discount = 0;) או בהשמה בכל ענף (הוסיפו else). שדות ואיברי מערך שונים: הם מקבלים אוטומטית את ערך ברירת המחדל של הטיפוס שלהם (0, false, null).
const: קבועים של זמן קומפילציה
const הוא ערך שנקבע בזמן קומפילציה. חייבים לאתחל אותו בביטוי קבוע (ליטרל, const אחר, או חשבון עליהם), והוא יכול להיות רק מספר, char, bool, string, enum או null.
פלט:
Price in EUR
120.00 EUR
350
שדה const הוא static באופן מרומז, כך שניגשים אליו דרך שם הטיפוס (Pricing.VatRate), וכתיבה של static const היא שגיאה. הקומפיילר מעתיק את הערך לכל מקום שמשתמש בו, ולכן קבועים לא עולים כלום בזמן ריצה, ולכן גם const ציבורי בספרייה צריך להיות ערך שלעולם לא ישתנה (כמו מספר הימים בשבוע).
readonly ו-static readonly
אפשר להשים לשדה readonly רק בהצהרה שלו או בבנאי של המחלקה שלו. אחרי הבנייה הוא לא יכול להשתנות. בניגוד ל-const, הערך שלו מחושב בזמן ריצה, יכול להיות מכל טיפוס, ויכול להיות שונה בין אובייקטים.
פלט:
ACC-17 opened 2024-05-02
Launch: 2020-01-15
basic, pro
השורות האחרונות מראות את אי-ההבנה הנפוצה: readonly מגן על השדה, לא על האובייקט שהוא מצביע עליו. Plans תמיד יפנה לאותו מערך, אבל האיברים של המערך עדיין יכולים להשתנות. לאוספים שבאמת בלתי ניתנים לשינוי השתמשו ב-ReadOnlyCollection<T> או ב-ImmutableArray<T>.
סיכום של שלושתם:
const | readonly | static readonly | |
|---|---|---|---|
| מתי הערך ידוע | בזמן קומפילציה | בזמן ריצה | בזמן ריצה |
| טיפוסים מותרים | מספרים, char, bool, string, enums, null | כל טיפוס | כל טיפוס |
| איפה מגדירים | בהצהרה בלבד | בהצהרה או בבנאי | בהצהרה או בבנאי סטטי |
| לכל אובייקט או משותף | משותף (סטטי באופן מרומז) | לכל אובייקט | משותף |
| משתנים מקומיים | כן (const int x = 1;) | לא | לא |
מוסכמות שמות
שמות ב-C# תלויים באותיות גדולות וקטנות (total ו-Total הם משתנים שונים) וחייבים להתחיל באות או בקו תחתון. המוסכמות של Microsoft, שכמעט כל קוד C# עוקב אחריהן:
- משתנים מקומיים ופרמטרים: camelCase (
orderCount,unitPrice). - קבועים, מאפיינים, מתודות, מחלקות ושדות ציבוריים: PascalCase (
MaxItems,VatRate). - שדות פרטיים: _camelCase עם קו תחתון בהתחלה (
_balance), מוסכמה נפוצה בקוד .NET. - משתנים בוליאניים נקראים כמו שאלות:
isActive,hasDiscount,canRetry. - הימנעו מקיצורים ומתחיליות הונגריות (
strName,iCount).
מילת מפתח של C# לא יכולה לשמש כשם, אלא אם מוסיפים לפניה @: var @class = "5B"; חוקי, וזה שימושי לפעמים בקוד שנוצר אוטומטית או בשמות שדות של JSON.
החלפה והשמה מרובה
מאז C# 7, tuples מאפשרים להשים לכמה משתנים בפקודה אחת, וזה נותן החלפה בשורה אחת:
פלט:
20 10
Lisbon: 545,000
הצורה השנייה היא פירוק: היא מצהירה על city כ-string ועל population כ-int מתוך tuple. עוד ב-tuples.
שאלות נפוצות
מה זה var ב-C#?
var אומר לקומפיילר להסיק את הטיפוס של משתנה מקומי מהערך שהוא מאותחל בו: var count = 5; מצהיר על int. המשתנה עדיין בעל טיפוס סטטי ולא יכול להחזיק מחרוזת מאוחר יותר. var עובד רק עבור משתנים מקומיים שמאותחלים באותה פקודה.
מה ההבדל בין const ל-readonly ב-C#?
const הוא קבוע של זמן קומפילציה: הערך שלו חייב להיות ידוע בזמן הקומפילציה (מספרים, מחרוזות, bool, null), הוא סטטי באופן מרומז, והערך מועתק לכל מקום שמשתמש בו. שדה readonly מוגדר פעם אחת בזמן ריצה, בהצהרה שלו או בבנאי, יכול להחזיק כל טיפוס, ויכול להיות שונה בכל אובייקט.
מתי כדאי להשתמש ב-static readonly במקום ב-const?
השתמשו ב-static readonly עבור ערך משותף שלא יכול להיות קבוע של זמן קומפילציה, כמו DateTime, מערך או אובייקט, או עבור ערך ציבורי שעשוי להשתנות בין גרסאות של ספרייה. מכיוון שערכי const מועתקים לתוך ה-assemblies הקוראים, שינוי של const ציבורי דורש קומפילציה מחדש של כל assembly שמשתמש בו.
מה פירוש "use of unassigned local variable" ב-C#?
שגיאה CS0165: הקומפיילר מצא מסלול שבו משתנה מקומי נקרא לפני שהושם לו ערך כלשהו. למשתנים מקומיים אין ערך ברירת מחדל ב-C#. אתחלו את המשתנה כשמצהירים עליו, או ודאו שכל ענף של ה-if/switch משים לו ערך לפני שמשתמשים בו.
כדאי להשתמש ב-var או בטיפוסים מפורשים ב-C#?
שתי האפשרויות בסדר; צוותים בוחרים סגנון. כלל נפוץ הוא להשתמש ב-var כשהטיפוס ברור מהצד הימני (var list = new List<string>();) ולכתוב את הטיפוס כשהוא לא ברור (decimal total = GetTotal();). קוד המקור של סביבת הריצה של .NET עוקב בערך אחרי הכלל הזה.