131. The difference between struct and class

רובנו נתקלנו בשני המושגים struct וclass מתוך העולם ה.netי

אך האם אי פעם הצלחתם לעמוד על ההבדלים בין class וstruct?

ובכן, ננסה להסביר את ההבדלים ביניהם.

מבחינה קונספטואלית, צריך לחשוב על struct בתור מבנה סטטי (immutable) המייצג מידע פשוט. (למשל, מספרים)

על class לעומת זאת צריך לחשוב בצורה object oriented – כלומר, בתור מבנה המייצג יישות לוגית, שבין השאר אפשר לרשת ממנה, ולשנות פונקציונאליות בעזרת חוקי הפולימורפיזם וכו’. (לדוגמה, מחלקה המייצגת צורה, שממנה יורשים משולש, מרובע ועיגול)

במה מתבטא הדבר מבחינה טכנית?

  • לstruct תמיד יהיה Constructor דיפולטי. לא נוכל לבטל את הConstructor הדיפולטי או לשנות את ההתנהגות שלו, לעומת class שנוכל גם לבטל את הConstructor הדיפולטי שלו, וגם לשנות את ההתנהגות שלו
  • הConstructor הדיפולטי של struct מאתחל את כל השדות שלו בערכים הדיפולטיים שלהם (ראו גם טיפ מספר 32)
  • אם לא נאתחל משתנה מסוג struct, תתבצע אוטומטית קריאה לConstructor הדיפולטי שלו, לעומת classש אם לא נאתחל משתנה מהסוג שלו, נקבל את הערך null
  • באופן כללי structים לא יכולים לקבל את הערך null (חוץ מהמקרה המיוחד שלNullable, ראו גם טיפ מספר 35)
  • בConstructor לא דיפולטי של struct, אנחנו מחויבים לאתחל את כל השדות שלו (אחרת הוא לא יתקמפל), בניגוד לclass בו אם לא נאתחל שדה בConstructor, הוא יאותחל בערך הדיפולטי שלו.
  • classים יכולים לרשת classים אחרים. לעומת זאת, לא ניתן לרשת מstruct.
  • עם זאת, גם classים וגם structים יכולים לממש ממשקים.
  • לclass בד”כ מוקצה זכרון על הHeap, ואילו לstruct בד”כ על הStack.
  • לכן structים ינוקו בד”כ החל מהמקום בו הם יצאו מהScope (למשל, החל מהמקום בו המשתנה הלוקאלי כבר לא מוגדר), לעומת classים שינוקו ברגע שהGarbage Collector יחליט שהגיע זמנם לעזוב.
  • מסיבה זו, לא יכולה להיות לstruct פונקצית Finalizer.
  • בנוסף, מאחר וstruct מוקצה בד”כ על הStack, מתקיים כי השמה של object לערכו גוררת Boxing. כנ”ל לגבי השמה של ממשק לערך של struct.
  • השמה של struct לתוך struct אחר, מכניסה אליו העתק של הstruct המקורי. כלומר, כתיבת השורה
1
MyStruct first = second;

תכניס לתוך first שכפול של second, אבל בפועל, שניהם לא יצביעו לאותו מקום בזכרון (כלומר, אם נשנה את first, זה לא ישנה את second)

  • כנ"ל לגבי קריאה לפונקציה: אם נעביר לפונקציה struct בתור פרמטר, הפונקציה למעשה תקבל העתק של הstruct (כלומר, אם נשנה את הפרמטר, זה לא ישנה את הstruct המקורי)

שימו לב להבדלים: הדברים החשובים לדעתי הם שערך של struct משוכפל בהשמה ובקריאה לפונקציה, הוא לא יכול לקבל את הערך null ולא ניתן לרשת ממנו.

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

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

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

(יש שיגידו שכלל אצבע הוא עד 16 בתים לstruct)

מקווה שהצלחתי לפתח אינטואיציה על ההבדלים בין struct לclass.

שבוע מובנה טוב

שתף