226. Dynamic binding

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

לדוגמה, נניח שיש לנו שתי מחלקות כאלה:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class IntegerList
{
public void Add(int value)
{
}
}
public class IntegerStack
{
public void Add(int value)
{
}
}

ונניח שיש פונקציה שמחזירה אחת מהן:

1
2
3
4
public static object GetIntegerListOrStack()
{
// ...
}

כעת נניח שקראנו לפונקציה הזו:

1
object integerDataStructure = GetIntegerListOrStack();

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

נוכל לעשות משהו כזה:

1
2
3
4
5
6
7
8
9
10
11
12
object integerDataStructure = GetIntegerListOrStack();
if (integerDataStructure is IntegerList)
{
IntegerList list = (IntegerList)integerDataStructure;
list.Add(6);
}
else if (integerDataStructure is IntegerStack)
{
IntegerStack stack = (IntegerStack)integerDataStructure;
stack.Add(6);
}

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


עד Framework 4.0 יכולנו לפתור בעיה זו רק באמצעות Reflection.

אלא שבReflection כתיבה כזאת היא לא הכי כיפית:

1
2
3
4
5
6
7
8
object integerDataStructure = GetIntegerListOrStack();
Type dataStructureType = integerDataStructure.GetType();
MethodInfo addMethod =
dataStructureType.GetMethod("Add", new[] { typeof(int) });
addMethod.Invoke(integerDataStructure, new object[] { (int)6 });

קריאה לפונקציה פשוטה, לקחה פה 3 שורות קוד.

בC# 4.0 נוסף Feature לשפה בשם Dynamic binding. הוא מאפשר לנו לבצע פעולה דומה לזו בסינטקס יותר כיפי:

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

הקוד הזה בעצם יבצע בסופו של דבר אותה פעולה – הפעלת הפונקציה Add עם הפרמטר 6.

מה קורה פה?

הdynamic הזה הוא Keyword שאומר לקומפיילר "אני יודע מה אני עושה".

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

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


מה קורה אם הMember לא קיים? בואו ננסה:

1
2
dynamic dynamicDataStructure = integerDataStructure;
dynamicDataStructure.Test(6);

נקבל את הException הבא:

RuntimeBinderException was unhandled: ‘IntegerList’ does not contain a definition for ‘Test’.

כלומר, נעוף בזמן ריצה, כיוון שלא קיים Member כזה.


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

עם זאת, הFeature הזה לא מחליף לנו את Reflection באופן מלא.

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

בנוסף, הדבר הזה לא מאפשר לנו לבצע ניתוח על האובייקט, למשל, לא נוכל לדעת באמצעות dynamic בלבד איזה Memberים יש לאיזשהו טיפוס, איזה Attributeים יש להם וכו’.

שבוע דינאמי טוב!

שתף