224. Multiple dynamic dispatch

דיברנו קצת על Single dynamic dispatch.

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

מה בדיוק Single כאן?

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

קיים קונספט מוכלל דומה שנקרא Multiple dynamic dispatch. הרעיון הוא שהניתוב למתודה בזמן ריצה לא נקבע עפ”י פרמטר בודד (הטיפוס של הinstance עליו מופעלת המתודה), אלא עפ”י מספר פרמטרים, שהם חלק מהטיפוסים שנשלחו לפונקציה.

למה הכוונה?

נניח שיש לנו את הפונקציה הבאה:

1
public static void InscribeInShape(Shape innerShape, Shape circumscribedShape)

זו פונקציה שמקבלת שתי צורות, וחוסמת צורה אחת בתוך השנייה.

עכשיו, אנחנו יכולים ליצור את מגוון הOverloadים הבאים:

1
2
3
4
public static void InscribeInShape(Square innerShape, Circle circumscribedShape)
public static void InscribeInShape(Triangle innerShape, Circle circumscribedShape)
public static void InscribeInShape(Circle innerShape, Triangle circumscribedShape)
public static void InscribeInShape(Circle innerShape, Square circumscribedShape)

אלה מספר אפשרויות של לחסום מעגל בתוך ריבוע או משולש, או לחסום משולש/ריבוע במעגל.

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

1
2
3
Square square;
Circle circle;
InscribeInShape(square, circle);

הקומפיילר ידע לנתב אותנו לOverload המתאים ביותר, במקרה שלנו ל

1
public static void InscribeInShape(Square innerShape, Circle circumscribedShape)

היינו רוצים שגם אם נכתוב קוד כזה:

1
2
3
Shape someShape = GetRandomShape();
Shape someOtherShape = GetRandomShape();
InscribeInShape(someShape, someOtherShape);

ושהמתודה עם הOverload המתאים ביותר תבחר בזמן ריצה, שהרי כרגע תמיד נבחר הOverload

1
public static void InscribeInShape(Shape innerShape, Shape circumscribedShape)

לקונספט הזה של בחירת מתודה בזמן ריצה עפ"י סוגי הפרמטרים, קוראים Multiple dynamic dispatch.


איך אפשר להשיג את זה בC#? עד C# 3.0 צריך לעבוד קצת קשה כדי לעשות משהו כזה:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void InscribeInShape(Shape innerShape, Shape circumscribedShape)
{
if ((innerShape == null) || (circumscribedShape == null))
{
throw new ArgumentException();
}
MethodInfo bestOverload =
typeof (ShapeUtilities).GetMethod("InscribeInShape",
new[] {innerShape.GetType(), circumscribedShape.GetType()});
bestOverload.Invoke(null,
new[] {innerShape, circumscribedShape});
}

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

לאחר מכן אנחנו מפעילים את הפונקציה עם הפרמטרים הנ"ל (ראו גם טיפ מספר 148)

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

מה זאת אומרת? אם, למשל, תהיה לנו את ההיררכיה הבאה:

1
Circle : Ellipse : Shape

ויהיו לנו רק את הOverloadים הבאים:

1
2
public static void InscribeInShape(Square innerShape, Ellipse circumscribedShape)
public static void InscribeInShape(Ellipse innerShape, Square circumscribedShape)

אז הקוד הבא:

1
2
3
Shape someShape = new Circle();
Shape someOtherShape = new Square();
InscribeInShape(someShape, someOtherShape);

יכנס לOverload

1
public static void InscribeInShape(Ellipse innerShape, Square circumscribedShape)

למרות הType הקונקרטי של someShape הוא Circle, ולא Ellipse.

זה די מגניב!


יש שפות שבהן שימוש בMultiple dispatch הוא יותר פשוט.

בשפת C# 4.0 נכנסה תמיכה לתוך השפה, כך שהרבה יותר קל להשתמש בזה.

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

ראו גם ב Wiki.

המשך יום דינאמי מרובה טוב!

שתף