227. How dynamic binding works

פעם קודמת הכרנו את הFeature החדש של C# 4.0 ששמו dynamic binding.

הפעם ננסה להבין איך הקסם שם עובד.

אז ניקח את הקוד שכתבנו פעם שעברה:

1
2
3
object integerDataStructure = GetIntegerListOrStack();
dynamic dynamicDataStructure = integerDataStructure;
dynamicDataStructure.Add(6);

ונסתכל מה קורה כשאנחנו פותחים אותו בReflector: נקבל משהו כזה:

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

1
2
3
4
private static class MyClassHelper
{
public static CallSite<Action<CallSite, object, int>> AddActionOnObjectAndInt;
}

והקוד שכתבנו מתרגם למשהו כזה: (שיניתי את השמות כדי שיהיה אפשר להבין)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
object integerDataStructure = GetIntegerListOrStack();
object dynamicDataStructure = integerDataStructure;
if (MyClassHelper.AddActionOnObjectAndInt == null)
{
MyClassHelper.AddActionOnObjectAndInt =
CallSite<Action<CallSite, object, int>>.Create(Binder.InvokeMember(
CSharpBinderFlags.ResultDiscarded, "Add", null, typeof (MyClass),
new CSharpArgumentInfo[]
{
CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
CSharpArgumentInfo.Create(
CSharpArgumentInfoFlags.Constant | CSharpArgumentInfoFlags.UseCompileTimeType, null)
}));
}
MyClassHelper.AddActionOnObjectAndInt.Target.Invoke(MyClassHelper.AddActionOnObjectAndInt, dynamicDataStructure, 6);

טוב, מחלקת העזר היא מחלקה סטטית פנימית השומרת CallSiteים. המטרה שלה היא שבכל פעם שנבצע איזושהי פעולה דינאמית (מתוך המחלקה שאנחנו נמצאים בה עכשיו), היא תשמור Cache שלה, כך שאם נקרא לAdd הזאת למשל עוד כמה פעמים, בפעמים הנוספות (הפעמים שהן לא הפעם הראשונה), ההפעלה תהיה יותר מהירה.

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

מהו CallSite? תכלס זו איזושהי מחלקה שיוצרת Delegateשאנחנו קוראים לו בסופו של דבר בקריאה הדינאמית. הDelegate נשמר בMember בשם Target.

במקרה שלנו, למשל, מאחר ואנחנו לא מנסים להכניס את ערך ההחזר שלנו מהפונקציה, נוצר Delgate מסוג Action. (זהו Delegate שלא מחזיר ערך!)

הDelegate מקבל את הCallSite, את האובייקט שעליו אנחנו מנסים להפעיל את הפונקציה (זהו משתנה מסוג object) ואת הפרמטרים שאנחנו רוצים לשלוח לפונקציה.

את הCallSite מאתחלים בתוך הif. באמצעות הפונקציה CallSite<T>.Create. נתעלם שנייה מהפרמטר שמועבר שם ונראה איך זה נראה:

בסופו של דבר מה שקורה זה שנקראת הפונקציה בשם MakeUpdateDelegate היוצרת את הProperty בשםTarget אותו אנחנו מפעילים.

איך הפונקציה עובדת? היא בודקת אם הקריאה שביצענו היא פשוטה (כלומר בלי ref וoutים). במידה וכן, היא פשוט מחזירה קריאה לפונקציה מתאימה במחלקה פנימית בשם UpdateDelegates.

(לאחת מהפונקציות בשם UpdateAndExecute, UpdateAndExecuteVoid או NoMatch משורשר עם מספר הפרמטרים שיש בקריאה, בהתאם לסוג הפונקציה שקראנו לה והאם נמצאה פונקציה מתאימה)

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

מה הפונקציות האלה עושות? אלה פונקציות שככל הנראה מחוללות.

מה שהן עושות זה מעדכנות בCallSite שלנו את הTarget. (לא כל כך ברורה הנקודה הזאת)

אחרי זה הן מוצאות בעזרת הBinder של המחלקה את המתודה שצריך להריץ. מה זה הBinder הזה?

זה לא הBinder שהכרנו לא מזמן, אלא מדובר בCallSiteBinder, איזשהו אובייקט שממפה לנו איך להריץ פעולה דינאמית.

מה זאת אומרת? אם נסתכל, למשל העברנו אליו איזשהו Binder הנוצר מBinder.InvokeMember. זהו Binder שיודע להריץ מתודה של C#.

באופן דומה יש Binderים המסוגלים לעשות דברים אחרים למשל, להריץ פעולה בינארית, להפעיל אינדקסר, לבצע קריאה/כתיבה לMember של הטיפוס וכו’.

המחלקה הסטטית Binder היא ספציפיות לC#. כל שפה שרוצה תמיכה בDynamic Binding, צריכה לספק CallSiteBinder ספציפי לשפה.

למשל, CallSiteBinder של שפת C# ידאג לכך שכשאנחנו מבצעים את פעולת החילוק 5/2, יחזור לנו 2. לעומת זאת CallSiteBinder של שפה אחרת (למשל IronPython) יכול לדאוג לכך שיחזור לנו דווקא 2.5.


באופן כללי, לא כל כך פשוט להבין איך כל המנגנון הזה עובד מבפנים.

אישית, לא הצלחתי לעקוב עד הסוף.

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

שתף