Friday, July 29, 2005

שלום לכולם,

קדם דבר

בואו נדבר קצת על ההבדלים בין Struct (להלן: מבנה) ל-Class (להלן: מחלקה). למי שרק עכשיו שומע על מבנה נסביר בתמצות מהו מבנה (מהפן הפרקטי). שאנו כותבים מחלקה חדשה הקוד יראה בערך ככה:

public class ClassName
{
// ...
}

אז שאנחנו עובדים עם מבנה זה יראה ככה:

public struct StructName
{
// ...
}

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

 

חלק א': על Stack ו-Heap, ועל Refrence type ו-Value type (או: “אני אמיתי ואתה לא!“)

נחזור ונכיר כמה מושגי יסוד: Value types ו-Reffrence types. כידוע, כל אובייקט במערכת יורש מ-System.Object, אך יש קבוצה קטנה של בעלי סגולה שיורשים גם מ-System.ValueType ומכאן מתחילים כל ההבדלים. נזכיר כעת כמה ValueTypes שהם סלברטאים: int, bool, char ו-enum. עכשיו נראה כמה Refrence Types שגם הם סלבריטאים: System.Web.UI.DropDownList, System.Windows.Forms.CheckBox ו-ArrayList. מניסיון קודם אנחנו רואים בבירור שיש הבדלים כלשהם.

אחרי שהראינו כי באופן אינטואטיבי קיים הבדל כלשהו, ניכנס עוד יותר לעומק ונדבר על Stack ו-Heap. ה-Stack וה-Heap הם מקומות המוקצים בזכרון המחשב ובו נמצאים האובייקטים שלנו.  כל Refrence type שניצור יווצר על ה-Heap, ובתוך בלוק הקוד שלנו יווצר לנו משתנה המצביע על אותו מקום ב-Heap רק שההפנייה הזאת ל-Heap יושבת ב-Stack. במילים פשוטות, שניצור DropDwonList הוא נוצר למעשה ב-Heap ושאנו עובדים עם אותו DropDownList אנו למעשה עובדים עם הפנייה שיושבת ב-Stack שמפנה לאותו מקום ב-Heap. מהסיבה הזאת אפשר ליצור מיליון הפניות ב-Stack לאותו מקום ב-Heap. נראה בדוגמה על מה דיברנו כרגע: 

public void someProcedure()
{
  System.Web.UI.DropDownList firstDDL = new DropDownList();
  System.Web.UI.DropDownList secondDDL = firstDDL;
 
  firstDDL.Items.Add(new ListItem(“SomeItem));
 
  // print how many items in firstDDL
  Response.Write( firstDDL.Items.Count + “ items in first DDL“ );
  // print how many items in secondDDL
  Response.Write( secondDDL.Items.Count + “ items in second DDL“ );
}

נעקוב בקצרה אחרי מה שעשינו, הצהרנו על Refrence type ובנינו לו שני הפניות. הוספנו להפנייה הראשונה פריט אחד. שהדפסנו כמה פריטים יש בכל DropDonwList נקבל שיש בכל הפניה פריט אחד, למרות שלא הוספנו פריט להפניה השנייה. כלומר, עבדנו למעשה בשני ההפניות האלו על אותו אובייקט בזכרון.

עכשיו נדבר על Stack. הרעיון מאחורי Stack זה לשמור מידע כמה שיותר זמין לתקופת הזמן שאנו עובדים איתו. ה-Stack באופן יחסי קטן באופן ניכר מה-Heap מהסיבה היא שזה הזכרון שאיתו רוב הזמן המעבד עובד איתו וככל שהוא יותר “ממוקד” ככה העבודה מהירה יותר. זה מתקשר ל-ValueTypes בכך שכל ValueType נשמר רק על ה-Stack ואין טיפת קשר ל-Heap. היות ואנו לא עובדים עם הפניות אנו תמיד עובדים עם אובייקטים ממשיים. ניישם את הדוגמה שהוזכרה למעלה על int32: 

public void someOtherProcedure()  { /* Changed on 30.7.05, Thanks Eran */
{
 int firstInt = 1;
 int secondInt =  firstInt;
 
  secondInt = secondInt + 1; // (secondInt += 1;) 
 
  // print first Int
  Response.Write(”first Int32 is: “ + firstInt.ToString() );
  // print second string
  Response.Write(”second Int32 is: “ + secondInt.ToString() );
}

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

 

חלק ב': ואיך כל זה קשור למבנים ומחלקות? (או: “נו, תכלס', למה שיהיה אכפת לי?”)

מבנה הוא ValueType (בדומה למחרוזת, מספרים, בוליאנים ו-Enums) ומחלקה היא Refrence Type (פחות או יותר כל השאר). נתחיל לדבר על הבדלים מבחינת תכנות:

1. אי-חובת כתיבת Constructors: (או: “אותי אתה לא צריך לאתחל“)

למחלקה תמיד חובה שיהיה Constructor אחד לפחות. כאשר כל ה-Constructorים של מחלקה הם Private - לא ניתן לממש את המחלקה. נראה דוגמה:  

 public class myClass
 {
  private myClass()
  { }
 }
 
myClass curClass = new myClass(); // Throws exception

למעשה את myClass מהדוגמה אי-אפשר לממש. אין לו שום Constructor שבאמת ניתן דרכו לממש את myClass.

לעומת זאת, מבנה תמיד תמיד תמיד אפשר לאתחל. תכתבו Constructor, אל תכתבו Constructor, תכתבו שה-Constructor הוא Private, למי אכפת?  

 public struct myStruct_NoConstructor
 {
 }
 
 public struct myStruct_PrivateConstructor
 {
  private myStruct_PrivateConstructor(int x)
  { }
 }
 
 public struct myStruct_SomeConstructor
 {
  public myStruct_SomeConstructor(int x)
  { }
 }
 
 
// No Exceptions will be thrown
myStruct_NoConstructor struct1 = new myStruct_NoConstructor();
myStruct_PrivateConstructor struct2 = new myStruct_PrivateConstructor();
myStruct_SomeConstructor struct3 = new myStruct_SomeConstructor();
 

שמתם לב? בין אם נכתוב או לא נכתוב Constructor יהיה אפשר לממש מבנה. את ההסבר לזה נראה בסוף הסעיף הבא. אבל ביינתים נראה בדוגמה שגם הרי לא חייבים את ה-Constructor של מספר כדי ליצור מופע של מספר.

2. אי-חובת איתחול (או: “ברגע שדיברת אליי, אני כבר פה“)

בזמן שניתן לממש כל מבנה בלי קשר לסטטוס ה-Constructor שלו, לא חייבים בכלל לאתחל אותו. מספיק לתת “הפנייה” למבנה והוא כבר מאותחל. ברגע שנכתוב הפנייה למבנה נוכל לגשת לכל ה-Public members שלו.  

 public struct myStruct
 {
  public int x;
 }
 
 // We do not initliize myStruct
 myStruct curStruct;
 
 // Change public X int value (no exception is thrown)
 curStruct.x = 1;
 
  // Print X (no exception is thrown)
 Console.Write(curStruct.x);

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

ברור גם למה זה קורה, הרי מבנה יושב ב-Stack שברגע שאנו מגדירים אובייקט שיושב שם ברור שלא מדובר באיזה הפנייה ל-Heap, אלא באובייקט לפני עצמו ולכן ה-CLR ישר שם מקום המיועד למבנה ב-Stack. כנ”ל ברור יקרה גם עם  int, enum ושאר Value Types.

3. מבנה לא יכול להיות null (או: “אני תמיד איתכם ברוחי“)

היות ותמיד ניתן לאתחל מבנה מרק ליצור אליו הפנייה - לא ייתכן שנדבר על מבנה שהוא null. למעשה, null מתייחס להפנייה ל-Heap שלא מצביעה לשום מקום. היות ואין שום הפנייה ל-Heap לא יכול להיות שמבנה שווה ל-null.

 myStruct curStruct;
if (curStruct==null) { } // Throws Exception

4. אין הורשה למבנה או ממבנה (או: “נדוניה וירושה”)

כל מה שהוא Value Type יורש בהכרח מ-System.ValueType שנותן לו את כל היכולות שהזכרנו ונזכיר. אך, מעבר ללרשת מ-System.ValueType שקורה באופן שקוף לנו, לא ניתן לבצע ירושה על ValueTypes. כלומר מבנים לא יכולים לרשת מבנים\מחלקות אחרות, ולא יכולים להיות אובייקטים מהם יורשים. דוגמה ידועה לזה היא שלא ניתן לרשת int וליצור SuperInt עם הרבה יותר פונקציונליות.

לכאורה מדובר בחסרון, אבל בגלל היעוד של מבנה מדובר למעשה בנכס היות וה-CLR מכריח אותנו לעבוד פשוט. (עוד על היעוד של מבנה בהמשך)

נחדד את ההבדלים בין מבנה למחלקה בנושא ירושה: כל מבנה הוא Sealed (לא ניתן לרשת ממנו והוא לא יכול לרשת), אי-אפשר להצהיר על Abstract struct (הרי אם אין ירושה ממבנה זה מיותר),  לא ניתן להצהיר על Protected members (הרי אם לא ניתן לרשת ממנו אז מה הטעם לכתוב משתנים שרק מי שיורש ממנו יכול לגשת אליהם?), ומבנה לא יכול לעשות Override למתודות שלא נורשות מ-System.Object.

5. אסור Destructor (או: “אני הולך לבד הביתה”)

במבנה כל ניסיון לכתוב Destrector (פונקציה שנקראת בזמן הריסת המבנה ע”י ה-garbage collector) תגרום לשגיאה. במחלקה אפשר לרשום Destructor (למרות שאלמלא אתם בונים מחדש תשתיות גישה לקבצים\מסדי-נתונים\... זאת תהיה טעות דרסטית לעשות את זה).

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

 // Will not throw compile error
 public class myClass_Destrcutor
 {
  public myClass_Destrcutor()
  { }
 
  ~myClass_Destrcutor()
  { }
 }
 
// Will throw Exception
 public struct myStruct_Destrcutor
 {
  ~myStruct_Destrcutor()
  { }
 }

שננסה לקמפל את myStruct_Destrcutor נקבל את השגיאה הבאה: Only class types can contain destructors.

הסיבה לכך נעוצה בתכולת החיים של משתנים ב-Stack ובה-Heap. למעשה ה-Heap מיועדת להחזיק בחיים משתנים מעבר לתחולת החיים של הפונקציה הנוכחית, ה-Thread הנוכחי, ואף מעבר לתכולת החיים של כלל התוכנית. לעומת זאת, משתנים ב-Stack הם משתנים מקומיים שחיים רק כל עוד שבלוק הקוד שלהם פעיל. בנוסף, מבחינה הגיונית - ב-#C קיימים Destructrים אך ורק בשביל תשתיות נרחבות שנבנו מחדש, שזה לא היעוד של מבנים. (עוד על היעוד של מבנים בהמשך)

6. אופן ההשוואה בין מבנים ומחלקות (או: “במחלקות זה האריזה, במבנה זה התוכן“)

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

 public class myClass
 {
  public myClass()
  { }  
 }
 
myClass firstClass = new myClass();
myClass firstClass_SamePointer = firstClass;
myClass secondClass = new myClass();
 
bool firstTest = (firstClass== firstClass_SamePointer); // Is true
bool secondTest = (firstClass == secondClass); // Is False
 
Console.WriteLine(firstTest.ToString() + " " + secondTest.ToString());

יצרנו שלושה מחלקות: מחלקה ראשונה שהיא myClass ברירת-מחדל, מחלקה שנייה שהיא הפנייה נוספת למחלקה הראשונה, ומחלקה שלישית שהיא גם myClass ברירת-מחדל. המחלקה הראשונה שווה למחלקה השנייה (היות ושניהן מצביעות לאותו מקום בזכרון). לעומת זאת, המחלקה הראשונה לא שווה למחלקה השלישית, למרות שיש להן בדיוק אותו תוכן. למעשה כל מה שאנו בודקים בהשוואה בין מחלקות זה הפניות לזכרון.

לעומת זאת, שאנו משווים בין מבנים חובה עלינו לבנות פונקציה שעושה השוואה בין התוכן שלהם.

 public struct myStruct_Equels
 {
  public int x;
 
  public override bool Equals(object OtherObj)
  {
   myStruct_Equels otherMyStruct_Equels  = (myStruct_Equels) OtherObj;
   return otherMyStruct_Equels.x == x;
  }
 
 }

יצרנו מבנה שעושה Override ל-Equels ובודק אם ה-X של מופע אחר של אותו מבנה שווה ל-X שלו.

myStruct_Equels firstStruct;
firstStruct.x = 1;
 
myStruct_Equels secondStruct;
secondStruct.x = 1;
 
Console.WriteLine((firstStruct.Equals(secondStruct)).ToString());

יצרנו שני מבנים מאותו סוג, הכנסנו להם אותו ערך ל-X ובדקנו אם הם שווים. התוצאה תהיה True. אם נשנה את הערך X של אחד מהם - התוצאה תהיה False.

יבואו המתחכמים ויגידו - אבל אתה יכול לממש מחלקה שגם הוא תבדוק השוואה לפי התוכן, ובכלל למה אני צריך לממש את הבדיקה של התוכן  במבנה?

דבר ראשון, אפשר לממש מחלקה שתעשה פחות או יותר הכל, אבל זה לא סטנדרט לדרוס את הפונקציות השוואה של מחלקות ולשנות להן לחלוטין את המשמעות.

דבר שני, אנחנו בכלל לא חייבים לממש את Equels של myStruct_Equels. זאת רק הייתה דוגמה.

 public struct myStruct_Equels
 {
  public int x;
 }

המימוש הזה של myStruct_Equels ירוץ בדיוק אותו דבר כמו זה שרשמנו למעלה, וה-CLR יעשה לבד את ההשוואה בין כל המשתנים שלו.

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

7. התייחסות פנימית (או: “אני בכלל הוא”)

תשומת לב במיוחד לסעיף הזה (והבא) בבקשה כי עליהם מבוססת הדוגמה בהמשך.

נביט שנייה על myClass: 

 public class myClass_ThisIsOtherThis
 {
  public myClass_ThisIsOtherThis()
  {
   this = new myClass_ThisIsOtherThis(); // Throws exception
  }
 }

בדוגמה הזאת ניסינו בתוך פונקציה כלשהי (במקרה היא ה-Constructor) להגיד this שווה משהו אחר. במחלקהזה בלתי אפשרי. הסיבה היא שבמחלקה ה-this הוא רק הפנייה למקום מאוד ספציפי בזכרון. כחלק מרעיון ה-managed code (קוד שמנהל לנו שימוש בזכרון) לא ניתן לעשות את זה. ולכן הקומפיילר זורק לנו שגיאת this is readonly.

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

public struct myStruct_ThisIsOtherThis
 {
  private void myStruct_ChangeThis()
  {
   this = new myStruct_ThisIsOtherThis(); // Will not throw exception
  }
}
 
public struct myStruct_ThisIsOtherThis
 {
  public int x;

  public myStruct_ChangeThis(int x)
  {
     this = new myStruct_ThisIsOtherThis(x+1); // Will not throw exception
  }
}

8. אתחול משתנים פנימיים (או: “אני בכלל לא פה”)

כאשר אנו בונים Members למחלקה הם מקבלים ערך של ברירת מחדל כאשר אנו מאתחלים את המחלקה (וכפי שידוע, לא ניתן לגשת למחלקה בלי שתהיה מאותחלת או בלי שה-Memebers יהיו סטטיים). נסביר בפשטות, אם נרשום משתנה פנימי למחלקה,  ברגע אתחול המחלקה המשתנה הפנימי יקבל ערך ברירת-מחדל. למשל מספר יקבל 0, מחרוזת תקבל “”, בוליאני יקבל 0 וכך הלאה. נדגים זאת: 

public class myClass_ValueOnInit
 {
  public bool innerBool;
  public int innerInt;
  public string innerString;
 
  public myClass_ValueOnInit()
  {
   Console.WriteLine(innerBool.ToString());
   Console.WriteLine(innerInt.ToString());
   Console.WriteLine(innerString.ToString());
  }
}
myClass_ValueOnInit myClass = new myClass_ValueOnInit();

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

public class myClass_ValueOnInit
 {
  public bool innerBool = false;
  public int innerInt = 0;
  public string innerString = "";
 
  public myClass_ValueOnInit()
  {
   Console.WriteLine(innerBool.ToString());
   Console.WriteLine(innerInt.ToString());
   Console.WriteLine(innerString.ToString());
  }
}

מדובר בערכים ברירת מחדל של ה-CLR שברגע שאנו נאתחל את המחלקה הם יקבלו את ערכים אלו.

נדגים מה קורה במבנה. נשנה את המחלקה למבנה: (באמצעות החלפת ה-“Class“ ב-“Struct“)

 public struct myStruct_ValueOnInit
 {
  public bool innerBool;
  public int innerInt;
  public string innerString;
  public myStruct_ValueOnInit(object blah)
  {
   Console.WriteLine(innerBool.ToString());
   Console.WriteLine(innerInt.ToString());
   Console.WriteLine(innerString.ToString());
  }
 }
 
// Throws Exception
myStruct_ValueOnInit myStruct = new myStruct_ValueOnInit(new object());

ברגע שניסינו לקמפל את המבנה הזה נקבל שגיאה שהמשתנים הפנימיים אינם מאותחלים. נכתוב מחדש את ה-Struct לשם מיקוד:

 public struct myStruct_ValueOnInit
 {
  public bool innerBool;
  public int innerInt;
  public string innerString;

 }

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

9. עוד הבדלים

לא חסרים עוד הבדלים בין מבנים למחלקות: ניתן לשלוח מבנה כ-ref או out בפונקציות פנימיות, לא ניתן לנעול מבנה (כדי שהוא יהיה thread-safe), וכיו”ב. אתם מוזמנים להמשיך לחקור את הנושא.

 

חלק ב': ייעוד המבנה (או: “עף כמו דבורה, עוקץ כמו פרפר”)

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

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

נראה דוגמה ונבין למה מחלקה לא מתאים להיות טיפוס נתונים קל.

 

חלק ג': דוגמה לשימוש נכון במבנה (או: “הלכתי לאיבוד, מה הקורדינטות של מיקומי הנוכחי?”)

תרחיש אמיתי לחלוטין. אנחנו בונים מערכת GIS (מערכת מפות). ברצוננו לציין ולעבוד עם 10,000-100,000 קורדינטות במסך תצוגה אחד. קורדינטה בכל מסך ישנם שילובים של שלוש סוגי קורדינטותה מהסוגים הבאים:

- קורדינטת ציר אחד (ציר X בלבד)

- קורדינטית שני צירים (צירי X,Y בלבד)

- קורדינטת שלוש צירים (צירי X,Y,Z)

 נממש את קורדינטה במחלקה.

יש לנו שתי אפשרויות:  1. לבנות שלושה מחלקות של קורדינטות ונעשה תנאי לבין איזה נממש, 2. לבנות מחלקה אחת שתכיל שלושה צירים ושלושה קונסטרקטורים.

נתחיל מהאפשרות הראשונה. אין טעם אפילו לנסות לממש אותה ההיות ומדובר על אפליקציית זמן אמת  שטוענת בכל מסך חדש עד 100,000 נתונים שונים. כלומר, לכל נתון כזה נצטרך להכניס לתנאי ולבדוק לאיזה מחלקה לממש אותו - 100,000 תנאיים על מחשב. בוא נגיד שמדובר ב-Overhead ממש ממש לא רצוי. למרות שמבחינת עיצוב המערכת מדובר במודל טוב, מבחינת ביצועים זה סיוט וספק אם זה יטען במסגרת זמן סבירה. יש עוד סיבות למה לא מדובר ברעיון טוב מבחינת ביצועים (צריך לבצע 100,000 המרות מהמחלקה\ממשק הבסיסי כדי לעבוד עם הנתונים האלו וכך הלאה).

האפשורת השנייה היא לממש מחלקה אחת שמכילה את כל שלושת הנתונים עם שלושה קונסטרקטורים.

public class myCord
 {
  public int x;
  public int y;
  public int z;
 
  public myCord(int x)
  {
   this.x = x;
  }
 
  public myCord(int x,int y)
  {
   this.x = x;
   this.y = y;
  }
 
  public myCord(int x,int y, int z)
  {
   this.x = x;
   this.y = y;
   this.z = z;
  }
}

(לצורך דיון לא ראיתי לנכון לסבך את הקוד עם Properties).

לפי מה שראינו המחלקה למעלה שקולה למחלקה הבאה:

public class myCord
 {
  public int x = 0;
  public int y = 0;
  public int z = 0;
 
  public myCord(int x)
  {
   this.x = x;
  }
 
  public myCord(int x,int y)
  {
   this.x = x;
   this.y = y;
  }
 
  public myCord(int x,int y, int z)
  {
   this.x = x;
   this.y = y;
   this.z = z;
  }
}

עוד פעם נקבל שמבחינת ביצועים אנחנו בבעיה, אם למשל נעבוד עם 100,000 קורדינטות מסוג X בלבד נקבל כי ישנם 200,000 מספרים מאותחלים שלא עושים בהם שימוש. גם זה Overhead מיותר לחלוטין שאין אפשרות לעמוד בו.

לפי מה שעשינו עד כה ראינו כי אנו צריכים טיפוס נתונים כלשהו שיכול להכיל את כל השלושת הנתונים בלי לאתחל אותם - תכונה ידועה של מבנה!

public struct myCord
 {
  public int x;
  public int y;
  public int z;
 
  public myCord(int x)
  {
   this.x = x;
  }
 
  public myCord(int x,int y)
  {
   this.x = x;
   this.y = y;
  }
 
  public myCord(int x,int y, int z)
  {
   this.x = x;
   this.y = y;
   this.z = z;
  }
}

קיבלנו מצב שבו לא יהיה שום Overhead שאינו נדרש. אין המרות, אין משתנים שלא בשימוש, אין תנאים. זה לא הרבה יותר טוב?

(במצב אמיתי גם היינו בונים Properties שהיו מגבילות גישה לנתוני Y,Z שאינם מאותחלים)

 

לסיכום

דיברנו על ההבדלים בין מבנה למחלקה, ראינו מאיפה נובעים ההבדלים האלו, מדוע קיים כזה דבר מבנה, והראינו מצב אחד מרבים בו מבנה הוא טיפוס נתונים קל הרבה יותר טוב ממחלקה. כמו כל דבר בדוט נט מדובר על לבחור את הכלי הנכון בזמן הנכון.

10/14/2005 6:47:37 PM (Jerusalem Standard Time, UTC+02:00)
השימוש במחרוזת להצגת ההבדל בין הSTACK והHEAP הוא לא נכון.
<br>כפי שאתה יודע מחרוזת היא למעשה אוביקט ולא VALUE TYPE ובנוסף היא ממששת התנהגות של IMMUTABLE, כך שכל שינוי יוצר מחרוזת חדשה בעצם..
10/14/2005 6:47:37 PM (Jerusalem Standard Time, UTC+02:00)
ובקשר לדוגמה האחרונה - ומה עם להעמיס על המחסנית עכשיו 100000 structs בגודל של 3 int (לא משנה שקואורדינטה זה בכלל מספר עשרוני..)? הרי אפילו שנגיד שבממוצע שליש מהערכים לא מאותחלים, וזה חוסך זמן למערכת, המקום עדיין מוקצה להם ומבוזבז, לא?
10/14/2005 6:47:37 PM (Jerusalem Standard Time, UTC+02:00)
ערן שלום,
<br>אכן, בדקתי ב-MSDN ואכן System.String אינו יורש מ-System.ValueType אלא רק System.Object. הסתמכתי על ספר שמסתבר שנפלה בו טעות. שיניתי את הדוגמה לעבוד עם int.
<br>
<br>Gardner שלום,
<br>כנראה שלא הסברתי את הנקודה טוב - אלמלא אתה תאתחל את המשתניים הפנימיים של Struct (באמצעות קונסטרקטור או באופן ידני למשתנים פומביים) הם יהיו unassigned. לעבוד עם משתנה ב-Struct שלא אותחל יזרוק שגיאת קומפיילר.
10/14/2005 6:47:37 PM (Jerusalem Standard Time, UTC+02:00)
ג'סטין שלום,
<br>בדוגמא האחרונה כתבת שהיינו צריכים להגביל את הגישה למשתנים שלא אותחלו באמצעות properties.
<br>אתה יכול בבקשה להסביר למה הכוונה ולתת דוגמא?
<br>תודה,
<br>חן
10/14/2005 6:47:37 PM (Jerusalem Standard Time, UTC+02:00)
חן שלום,
<br>
<br>מצ&quot;ב קישורים מתאימים ל-MSDN בנושא Properties:
<br>
<br>Properties Tutorial:
<br><a target="_new" href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/csref/html/vcwlksimplepropertiestutorial.asp">http://msdn.microsoft.com/library/default.asp?url=/library/en-us/csref/html/vcwlksimplepropertiestutorial.asp</a>
<br>
<br>
<br>Adding a Property to a C# Class:
<br><a target="_new" href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cscon/html/vctskaddingcproperty.asp">http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cscon/html/vctskaddingcproperty.asp</a>
Name
E-mail
Home page

Comment (HTML not allowed)  

Enter the code shown (prevents robots):