256. About boxing and custom casts operators

[מבוסס על הפוסט הבא]

כולנו מכירים הסבות Custom של הFramework, למשל:

1
2
double real = 3.14;
int integer = (int) real; // 3

מה שקורה כאן זה בעצם שאין שום קשר של ירושה בין int וdouble, אבל מתבצעת הסבה ע"י Custom explicit cast. (ראו גם טיפ מספר 197)

בעיה שבד"כ נתקלים בה בהקשר זה היא הבאה: נניח שיש לנוobject שמחזיק double:

1
2
3
double real = 3.14;
object boxed = real;
int integer = (int) boxed; // Boom!

משום מה, הקוד הזה שנראה בערך אותו הדבר, לא עובד…

אבל אם נעשה משהו כזה:

1
2
3
double real = 3.14;
object boxed = real;
int integer = (int) (double) boxed; // 3

זה יעבוד.


מה בעצם קורה כאן?

כאשר אנחנו מכניסים לobject אובייקט שהוא Value-Type, מתבצע תהליך שנקרא Boxing. בתהליך הזה הValue-Type נעטף ע"י איזשהו אובייקט על הHeap שמצביע אליו.

נניח שיש לנו טיפוס A וטיפוס B.

נניח שיש לנו אובייקט מסוג A ואנחנו מעוניינים להמירו בטיפוס מסוג B בצורה Explicit.

הקומפיילר יודע להבחין בין שני סוגים של הסבות:

  • הסבה ראשונה: Up-cast – הסבה כזו אומרת את הדבר הבא: יש לי יותר ידע מהקומפיילר, אני יודע שהאובייקט A שאני מחזיק הוא למעשה אובייקט מסוג B, ואני דורש מהקומפיילר להתייחס אל האובייקט כאובייקט מסוג B.
  • הסבה שנייה: Custom cast – הסבה כזו אומרת את הדבר הבא: אני יודע שהאובייקט שאני מחזיק הוא מסוג A, אבל אני יודע שיש איזשהי דרך להמיר אותו לסוג B. אני דורש מהקומפיילר לבצע את ההמרה.

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


שאלה המתבקשת היא למה? למה הקומפיילר מפרש את הקריאה שלנו בתור בקשה לעשות unboxing לint? למה שלא ינסה לבדוק מהו הטיפוס האמיתי של האובייקט boxed וינסה משם לחפש דרך אחרת להפוך אותו לint?

התשובה היא כזאת: ביצוע פעולה של unboxing היא פעולה יחסית בזבזנית. כעת נניח שאנחנו רוצים לבצע unboxing ואז לחפש אופרטור הסבה מתאים, ואז להפעיל אותו. דבר ראשון, הפעולה שמתוארת כאן היא אפילו יותר כבדה מunboxing, שהרי אחר כך אנחנו מחפשים פונקציית המרה מתאימה וקוראים לה. דבר שני, הפעולה התמימה הזאת של "הסבה לint", הייתה מתקמפלת בסופו של דבר למספר שורות בIL, בניגוד לפעולה בודדת של unbox:

1
2
3
ldloc.1
unbox.any int32
stloc.2

אוקיי, ואם בכל זאת בא לי שהקומפיילר יבצע את התהליך המסובך שתואר למעלה?

אז זה אפשרי, בשביל זה בדיוק קיימת הפונקציה Convert.ToInt32, המבצעת חישוב הדומה לחישוב המסובך הנ"ל 😃:

1
2
3
double real = 3.14;
object boxed = real;
int integer = Convert.ToInt32(boxed); // 3

שבוע עם הסבות בטוחות טוב!

שתף