321. Implementing the virtual method pattern in C# part 3

אז ראינו בפעם הקודמת איך אפשר לממש פונקציות וירטואליות בשפה שתומכת בפונקציות סטטיות וDelegateים לפונקציות סטטיות.

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

אם במחלקה יש הרבה מתודות וירטואליות, למשל 100 מתודות וירטואליות, באופן אוטומטי במימוש זה, כל Instance יתחיל עם 100 Delegateים!

אז מה אפשר לעשות?

מה שהCLR בעצם עושה זה מקבץ את כל המתודות הוירטואליות לאיזה מבנה שנקרא Virtual Table, בצורה סטטית, כך שהDelegateים נוצרים פעם אחת עבור כל הInstanceים.

לדוגמה, הקוד הזה:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Square : Shape
{
public double Edge { get; set; }
public override double GetArea()
{
return this.Edge * this.Edge;
}
public override double GetPerimeter()
{
return 4 * this.Edge;
}
}

יתרגם למשהו כזה:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Square : Shape
{
public double Edge { get; set; }
public static double GetArea(Square _this)
{
return _this.Edge * _this.Edge;
}
public static double GetPerimeter(Square _this)
{
return 4 * _this.Edge;
}
}

וגם תיווצר איזושהי מחלקה כזאת:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
internal sealed class VTable
{
public delegate double GetPerimeterSignature(Shape _this);
public delegate double GetAreaSignature(Shape _this);
private readonly GetPerimeterSignature mGetPerimeter;
private readonly GetAreaSignature mGetArea;
public VTable(GetPerimeterSignature getPerimeter,
GetAreaSignature getArea)
{
mGetPerimeter = getPerimeter;
mGetArea = getArea;
}
public GetPerimeterSignature GetPerimeter
{
get { return mGetPerimeter; }
}
public GetAreaSignature GetArea
{
get { return mGetArea; }
}
}

כך שיאותחל Instance שלה באופן סטטי למחלקה Square:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public abstract class Shape
{
internal VTable mTable;
}
public class Square : Shape
{
private static VTable sTable =
new VTable(InnerGetArea, InnerGetPerimeter);
public Square()
{
mTable = sTable;
}
public double Edge { get; set; }
public static double InnerGetArea(Shape _this)
{
return GetArea((Square) _this);
}
public static double InnerGetPerimeter(Shape _this)
{
return GetPerimeter((Square) _this);
}
public static double GetArea(Square _this)
{
return _this.Edge * _this.Edge;
}
public static double GetPerimeter(Square _this)
{
return 4 * _this.Edge;
}
}

כעת כשנכתוב קוד כזה:

1
2
Shape shape = new Square() { Edge = 3 };
Shape.GetPerimeter(shape);

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

1
2
Shape shape = new Square() { Edge = 3 };
shape.mTable.GetPerimeter(shape);

כך שבעצם נכנס לפונקציה שרצינו! 😃

כי אתחלנו באופן סטטי בSquare את הMember ששמו sTable להיות VTable שממפה את המתודות למתודות הנכונות, ובכל יצירה של Square, אנחנו מאתחלים אותו עם הVTable המתאים.


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

1
2
3
RegularTriangle triangle = new RegularTriangle();
Square square = new Square();
square.mTable.GetPerimeter(triangle);

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


הסדרה הזאת מבוססת על הסדרה Implementing the virtual method pattern in C# של Eric Lippert. בסה"כ אני חושב שזה מסביר בצורה טובה איך עובדות בגדול פונקציות וירטואליות. אם אהבתם, אתם מוזמנים לקרוא את הפוסטים המקוריים בבלוג של Eric Lippert.

סופ"ש וירטואלי טוב!

שתף