35. Nullabe types

מדי פעם כשאנחנו כותבים קוד, אנחנו נתקלים בבעיה הנפוצה הבאה:

מדי פעם אנחנו כותבים פונקציה שמחזירה Value type, כגון int, long, bool וכו’.

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

אפשרות אחת היא לזרוק exception, אבל זו אפשרות פחות מוצלחת, מאחר וexceptionים אינם אמורים להיות חלק מהflow של האפליקציה.

אפשרות שנייה היא להחזיר ערך “לא תקין” המעיד על כך שקיבלנו ערך לא תקין.

למשל, הפונקציה IndexOf של string מחזירה ערך -1 כאשר לא נמצא אינדקס מתאים לערך המבוקש.

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

היינו רוצים להחזיר ערך קבוע שמעיד על שגיאה. כאשר אנחנו עובדים עם Reference types אנחנו יכולים תמיד להחזיר null.

לצערנו (ולפעמים אנחנו גם מודים על כך), אנחנו לא יכולים להחזיר null כאשר ערך ההחזר הוא value type.

בframework 2.0 המציאו פתרון. שמו הוא Nullable types.

במקום לכתוב פונקציה כזאת:

1
2
3
4
5
6
7
8
9
10
public static int IndexOf(string source,
string value)
{
if (source.Length == 0)
{
return -1;
}
// Write other cases here.
}

נוכל להחליף את ערך ההחזר להיות Nullable<int> ולהחזיר null במקרה שקיבלנו ערך לא תקין:

1
2
3
4
5
6
7
8
9
10
public static Nullable<int> IndexOf(string source,
string value)
{
if (source.Length == 0)
{
return null;
}
// Write other cases here.
}

יש syntactic sugar שעשו בשפה, כדי שנוכל לכתוב במקום Nullable<int> פשוט int?:

1
2
3
4
5
6
7
8
9
10
public static int? IndexOf(string source,
string value)
{
if (source.Length == 0)
{
return null;
}
// Write other cases here.
}

קריאה לפונקציה תראה כעת כך:

1
2
3
4
5
6
int? index = IndexOf("Banana", "nana");
if (index != null)
{
// ...
}

או כך:

1
2
3
4
5
6
int? index = IndexOf("Banana", "nana");
if (index.HasValue)
{
// ...
}

במקום כך:

1
2
3
4
5
6
int index = IndexOf("Banana", "nana");
if (index != -1)
{
// ...
}

נסביר קצת על Nullable<T>:

זהו value type (struct) שעוטף value typeים אחרים. יש implicit cast מT הנעטף לNullable<T>.

כלומר נוכל לכתוב קוד מהסגנון

1
2
bool? found = true;
int? number = 4;

בנוסף הוא "יכול לקבל" ערך null.

למה "יכול לקבל" בגרשיים? באופן כללי value typeים לא יכולים לקבל null.

כשאנחנו כותבים שורה כזאת:

1
bool? found = null;

הקומפיילר מקמפל אותה למשהו כזה:

1
2
3
4
L_0001: ldloca.s found
L_0003: initobj [mscorlib]System.Nullable`1
L_0009: ldloc.0
L_000a: stloc.1

שזה שקול לכתיבה:

1
Nullable<bool> found = new Nullable<bool>();

לכן nullable types כמו כל value type לא באמת מקבלים את הערך null.

לכן נוכל לגשת ללא חשש לProperty שנקרא HasValue.

עם זאת, השוואה לnull והצבת null מתרגמים מאחורי הקלעים לפעולות על הProperty HasValue, ולכן נוכל לכתוב בצורה האינטואיטיבית שהזכרתי מעלה.

סופ"ש כבר לא גנרי מצוין

שתף