225. Binder

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

אני רוצה להסביר קצת על איך הפונקציה הזאת עובדת, כי זה יכול לעזור לנו לכתוב דברים דומים:

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

1
protected override MethodInfo GetMethodImpl(string name, BindingFlags bindingAttr, Binder binder, CallingConventions callConv, Type[] types, ParameterModifier[] modifiers)

של RuntimeType.

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

מה זה Binder?

אם נזכר בטיפים על Reflection (מספרים 136-151), אמרנו שמה שקורה בזמן קריאה לפונקציה בעזרת Reflection, הוא מיפוי של האובייקטים שאנחנו שולחים לפונקציה לפרמטרים שלה, באמצעות תהליך שנקרא Late Binding.

Binder הוא בעצם מנגנון שמאפשר לנו לשנות את המיפוי הזה.

ממה הוא מורכב?

הוא מכיל את הפונקציות הבאות:

1
2
3
4
5
6
7
8
9
public abstract class Binder
{
public abstract FieldInfo BindToField(BindingFlags bindingAttr, FieldInfo[] match, object value, CultureInfo culture);
public abstract MethodBase BindToMethod(BindingFlags bindingAttr, MethodBase[] match, ref object[] args, ParameterModifier[] modifiers, CultureInfo culture, string[] names, out object state);
public abstract object ChangeType(object value, Type type, CultureInfo culture);
public abstract void ReorderArgumentArray(ref object[] args, object state);
public abstract MethodBase SelectMethod(BindingFlags bindingAttr, MethodBase[] match, Type[] types, ParameterModifier[] modifiers);
public abstract PropertyInfo SelectProperty(BindingFlags bindingAttr, PropertyInfo[] match, Type returnType, Type[] indexes, ParameterModifier[] modifiers);
}

מה אנחנו רואים כאן? לFieldInfo וMethodBaseשהכרנו מReflection יש בעצם איזושהי פונקציה מתאימה שעושה לנו את הBinding אליו.

בנוסף, יש פה פונקציה מעניינת שנקראת ChangeType הנקראת בעת קריאה למתודה (בעזרת Invoke), ומאפשרת לנו להשפיע על איך אובייקט ישלח לפונקציה שלנו.


מה שאותנו מעניין הוא דווקא הפונקציה

1
public abstract MethodBase SelectMethod(BindingFlags bindingAttr, MethodBase[] match, Type[] types, ParameterModifier[] modifiers);

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

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


למה עוד זה שימושי? אני אכתוב שימוש שהיה לנו עם זה:

יש לנו מחלקה שיכולה לקבל כל מיני סוגים של הודעות כInput.

המשתמש יכול לטפל בהודעות מסוג מסוים במתודה משלו ע"י כתיבת מתודה בודדת והשמה של Attribute מסוים מעליה:

1
2
3
4
5
6
7
8
9
10
11
12
public class UserHandler
{
[Handler]
private void HandleCircle(Circle circle)
{
}
[Handler]
private void HandleDog(Dog dog)
{
}
}

ברגע שמגיעה הודעה היא יודעת להגיע למתודה (הבודדת) המתאימה.

בהתחלה זה מומש ע"י החזקה של Dictionary שממפה טיפוסים למתודות, ואז מפנה למתודה המתאימה ע"י חיפוש הGetType של האובייקט בDictionary.

אבל זה לא עבד. למה לא עבד? כי אם נניח הייתה לי חתימה כזאת:

1
2
3
4
[Handler]
private void HandleShape(Shape shape)
{
}

אז זה לעולם לא היה נכנס אליה, כיוון שאין Instance שהטיפוס הקונקרטי שלו הוא Shape.

ככה למשל לTriangle וSquare לא היה שום טיפוס שיש לו מתודה מתאימה במחלקה שלי.

אפשר לחשוב על איך לממש משהו שישווה Typeים עפ"י עדיפות וימצא את הType הכי מתאים לType שהתקבל, אלא שזה קצת מסובך לממש דבר כזה…

במקום זאת, אפשר להשתמש בBinder שבא עם הFramework שימצא את המתודה עם החתימה הכי מתאימה! 😃


אלו מכם שינסו ליצור instance של Binder, יתאכזבו לשמוע שזו מחלקה אבסטרקטית, ושכל המימושים שלה בFramework הם internal (טיפ מספר 213).

מסתבר שאפשר להשתמש במימוש הדיפולטי ע"י גישה לProperty הסטטי Type.DefaultBinder.

סופ"ש מעולה שלא כבול לשום דבר.

שתף