C# היא שפה עם טיפוסים סטטיים: לכל משתנה, שדה וביטוי יש טיפוס שידוע בזמן קומפילציה. לשפה יש קבוצה של טיפוסים מובנים עם מילות מפתח משלהם (int, double, string...). כל מילת מפתח היא כינוי לטיפוס ב-namespace System, כך ש-int ו-System.Int32 הם אותו טיפוס, ו-string ו-System.String הם אותו טיפוס.
הטיפוסים המובנים
| טיפוס | טיפוס .NET | גודל | טווח או דיוק | ליטרל |
|---|---|---|---|---|
bool | Boolean | בית אחד | true או false | true |
byte | Byte | בית אחד | 0 עד 255 | 200 |
sbyte | SByte | בית אחד | -128 עד 127 | -5 |
short | Int16 | 2 בתים | -32,768 עד 32,767 | 1200 |
ushort | UInt16 | 2 בתים | 0 עד 65,535 | 60000 |
int | Int32 | 4 בתים | -2,147,483,648 עד 2,147,483,647 | 42 |
uint | UInt32 | 4 בתים | 0 עד 4,294,967,295 | 42u |
long | Int64 | 8 בתים | בערך ±9.2 × 10^18 | 42L |
ulong | UInt64 | 8 בתים | 0 עד בערך 1.8 × 10^19 | 42ul |
float | Single | 4 בתים | בערך ±3.4 × 10^38, ~6 עד 9 ספרות | 2.5f |
double | Double | 8 בתים | בערך ±1.8 × 10^308, ~15 עד 17 ספרות | 2.5 או 2.5d |
decimal | Decimal | 16 בתים | בערך ±7.9 × 10^28, 28 עד 29 ספרות | 2.5m |
char | Char | 2 בתים | יחידת קוד UTF-16 אחת, U+0000 עד U+FFFF | 'A' |
string | String | הפניה | טקסט בכל אורך, בלתי ניתן לשינוי | "hello" |
object | Object | הפניה | הבסיס של כל טיפוס | new object() |
ליטרל של מספר שלם בלי סיומת הוא int, או הראשון מבין uint, long ו-ulong שיכול להכיל אותו אם הוא גדול מדי ל-int, וליטרל עם נקודה עשרונית הוא double. זו הסיבה ש-float f = 2.5; ו-decimal d = 2.5; לא עוברים קומפילציה: הליטרל הוא double, ואין המרה מרומזת מ-double ל-float או ל-decimal. הוסיפו את הסיומת: 2.5f, 2.5m.
כל טיפוס מספרי חושף את הגבולות שלו כקבועים:
פלט:
byte: 0 to 255
short: -32768 to 32767
int: -2147483648 to 2147483647
long: -9223372036854775808 to 9223372036854775807
decimal max: 79228162514264337593543950335
double max: 1.798E+308
sizes: int 4, long 8, double 8, char 2
255 161 3000000000
בפועל רוב הקוד משתמש בארבעה מהם: int לספירות ולאינדקסים, long למזהים ולסכומים גדולים, double למדידות ולמתמטיקה, ו-decimal לכסף.
decimal מול double
double (ו-float) מאחסנים מספרים בבינארי. לרוב השברים העשרוניים, כולל 0.1, אין צורה בינארית מדויקת, ולכן הם נשמרים כערך הקרוב ביותר שניתן לייצג, ושגיאות קטנות מצטברות. decimal מאחסן מספר שלם בבסיס 10 ועוד קנה מידה, כך ש-0.1 הוא בדיוק 0.1.
פלט:
False
True
0.30000000000000004
0.3
double sum == 1.0: False
decimal sum == 1.0: True
Total: 59.97
Split three ways: 19.99
השתמשו ב-decimal לכסף, למחירים, למס ולכל דבר שאדם יחבר ביד וישווה. הוא שומר על אפסים בסוף מהקלט שלו (1.50m מודפס כ-1.50) ויש לו 28 עד 29 ספרות משמעותיות.
השתמשו ב-double למדידות פיזיקליות, לסטטיסטיקה, לגאומטריה ולמשחקים. הוא מהיר פי כמה, משתמש בחצי מהזיכרון, והטווח שלו עצום. אף פעם אל תשוו שני doubles מחושבים עם ==; השוו את ההפרש מול סף סבילות: Math.Abs(x - y) < 1e-9.
decimal הוא לא קסם: 1m / 3m הוא עדיין 0.3333333333333333333333333333, וחשבון עם double לא זורק בחלוקה באפס (הוא מפיק אינסוף, או NaN עבור 0.0 / 0.0), בעוד שחלוקה באפס של decimal ושל מספרים שלמים זורקת DivideByZeroException.
גלישה של מספרים שלמים ו-checked
חשבון של מספרים שלמים שחורג מהטווח של הטיפוס גולש מסביב כברירת מחדל, בשקט. הקומפיילר תופס גלישה בביטויים קבועים, אבל לא כשהערכים מגיעים ממשתנים:
פלט:
-2147483648
OverflowException
-1294967296
2147483648
checked(...) או בלוק checked { ... } הופכים גלישה ל-OverflowException. אפשר גם להפעיל את זה לכל הפרויקט עם <CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>. כתיבה של int x = int.MaxValue + 1; עם קבועים היא שגיאת קומפילציה (CS0220), כי הקומפיילר יכול לראות את הגלישה.
השורה האחרונה מראה את התיקון לרוב באגי הגלישה: המירו לטיפוס רחב יותר לפני החישוב. (long)(max + 1) היה שגוי, כי החיבור כבר גלש כשההמרה רצה.
טיפוסי ערך וטיפוסי הפניה
כל טיפוס ב-C# הוא טיפוס ערך או טיפוס הפניה, וההבדל קובע מה הצבה עושה.
- טיפוסי ערך: כל הטיפוסים המספריים,
bool,char,DateTime, enums, וכלstruct. המשתנה מכיל את הנתונים; הצבה מעתיקה אותם. - טיפוסי הפניה:
string, מערכים,object, וכלclass. המשתנה מכיל הפניה לאובייקט; הצבה מעתיקה את ההפניה, כך ששני המשתנים רואים את אותו אובייקט.
פלט:
a=10 b=99
first[0]=99
s1=cat s2=cats
string הוא טיפוס הפניה שמתנהג בפועל כמו ערך: הוא בלתי ניתן לשינוי, ו-== משווה את הטקסט, לא את ההפניות. עוד על טיפוסי ערך שהמשתמש מגדיר ב-structs.
char ו-bool
char הוא יחידת קוד UTF-16 אחת, שנכתבת בגרשיים בודדים. מתחת לפני השטח הוא טיפוס מספרי, ולכן הוא מומר ל-int ותומך בחשבון:
פלט:
66
C
True
True
7
b
True
ל-bool יש בדיוק שני ערכים, והוא לא מומר ממספרים או אליהם: if (count) היא שגיאת קומפילציה, לא בדיקה של שונה מאפס. כתבו if (count != 0). תווים מחוץ ל-Basic Multilingual Plane, כמו רוב האימוג'י, תופסים שני ערכי char (surrogate pair), כך שה-Length של string סופר יחידות UTF-16, לא תווים נראים.
ערכי ברירת מחדל
שדות, איברי מערך ו-default(T) מקבלים את ערך ברירת המחדל של הטיפוס: אפס למספרים, false ל-bool, '\0' ל-char, ו-null לטיפוסי הפניה. משתנים מקומיים לא מקבלים ברירת מחדל, וחייבים להציב להם ערך לפני השימוש.
פלט:
0
0
False
True
0001-01-01
0,0,0
מאז C# 7.1 אפשר לכתוב את הליטרל default בלי הטיפוס כשהקומפיילר יכול להסיק אותו (int x = default;). טיפוס ערך לא יכול להיות null, ובשביל זה קיים int? (nullable value type); ראו nullable types.
מספרים שלמים בגודל נייטיב ומספרים גדולים
קיימים עוד שני טיפוסים מספריים למקרים מיוחדים. nint ו-nuint (C# 9) הם מספרים שלמים בגודל של מצביע, 32 או 64 ביט לפי הפלטפורמה, ומשמשים ב-interop ובקוד ברמה נמוכה. System.Numerics.BigInteger מחזיק מספרים שלמים בכל גודל, במחיר של מהירות:
using System.Numerics;
BigInteger factorial = 1;
for (int i = 2; i <= 30; i++) factorial *= i;
// 265252859812191058636308480000000
שאלות נפוצות
אילו טיפוסי נתונים יש ב-C#?
הטיפוסים המובנים הם המספרים השלמים (sbyte, byte, short, ushort, int, uint, long, ulong), טיפוסי הנקודה הצפה float ו-double, decimal לחשבון עשרוני מדויק, char לתו UTF-16, bool, וטיפוסי ההפניה string ו-object. כל מילת מפתח היא כינוי לטיפוס של .NET: int הוא System.Int32, string הוא System.String.
להשתמש ב-decimal או ב-double לכסף ב-C#?
השתמשו ב-decimal. double מאחסן שברים בינאריים, כך שערכים כמו 0.1 לא ניתנים לייצוג מדויק וסכומים סוטים (0.1 + 0.2 == 0.3 הוא false). decimal מאחסן ספרות בבסיס 10, שומר 28 עד 29 ספרות משמעותיות, ונותן את התוצאות שרואי חשבון מצפים להן. double מהיר יותר ויש לו טווח גדול בהרבה, אז השתמשו בו למדע, לגרפיקה ולמדידות.
מהו הערך המקסימלי של int ב-C#?
int.MaxValue הוא 2,147,483,647 ו-int.MinValue הוא -2,147,483,648 (מספר שלם של 32 ביט עם סימן). הוספת 1 ל-int.MaxValue גולשת בשקט ל-int.MinValue, אלא אם הקוד רץ בהקשר checked, ואז נזרקת OverflowException. השתמשו ב-long (עד בערך 9.2 קווינטיליון) לערכים גדולים יותר.
מה ההבדל בין float ל-double ב-C#?
float הוא 32 ביט עם בערך 6 עד 9 ספרות משמעותיות; double הוא 64 ביט עם בערך 15 עד 17. ליטרלים של נקודה צפה הם double כברירת מחדל, כך ש-float צריך את הסיומת f: float speed = 2.5f;. השתמשו ב-double אלא אם זיכרון או API (כמו Vector3 של Unity) דורשים float.
מה ההבדל בין טיפוסי ערך לטיפוסי הפניה ב-C#?
משתנה מטיפוס ערך (int, double, bool, DateTime, כל struct) מחזיק את הנתונים עצמם, והצבה מעתיקה אותם. משתנה מטיפוס הפניה (string, מערכים, כל class) מחזיק הפניה לאובייקט ב-heap, והצבה מעתיקה את ההפניה, כך ששני משתנים יכולים להצביע על אותו אובייקט.