151. Delegate CreateDelegate

ראינו בטיפים הקודמים כיצד ניתן להפעיל מתודה באמצעות הMethodInfo שמייצג אותה. (טיפ מספר 148)

כמו שאמרתי אז, הרצת מתודה באמצעות Invoke היא יותר איטית, ואנחנו עשויים להרגיש בזה כאשר אנחנו מריצים את המתודה פעמים רבות (למשל, בתוך לולאה).

אפשר לבצע אופטימיזציה באמצעות delegateים:

במידה ואנחנו יודעים את החתימה של הפונקציה (כלומר מה הפרמטרים וערך ההחזר שלה), אנחנו יכולים ליצור delegate שיקרא לפונקציה שלנו. את זה נוכל לעשות באמצעות הפונקציה Delegate.CreateDelegate.

Overload אחד של הפונקציה הזאת מאפשר לנו ליצור delegate כזה עפ”י השם של הפונקציה בלבד:

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

1
2
3
4
5
6
7
8
9
10
11
12
Func<string, object[], string> format =(Func<string, object[], string>)
Delegate.CreateDelegate
(typeof(Func<string, object[], string>),typeof(string), "Format");
for (int i = 0; i < 10000; i++)
{
Console.WriteLine(format("{0} in hex is {1}",
new object[] {i, i.ToString("X")}));
//Console.WriteLine(string.Format("{0} in hex is {1}",
// new object[] {i, i.ToString("X")}));
}

שימו לב לסינטקס – אנחנו מעבירים את החתימה של המתודה שאנחנו מעוניינים לקרוא לה בתור פרמטר ראשון, את הטיפוס (הType) לו שייכת המתודה בתור פרמטר שני ואת השם של המתודה בתור פרמטר שלישי. לבסוף, אנחנו צריכים לעשות הסבה לטיפוס של הdelegate שלנו – זה בעצם סוד הקסם של האופטימיזציה.

אם אנחנו רוצים להיות יותר מדויקים, קיים גם overload שמקבל את הMethodInfo במקום את השם של הפונקציה (ואז לא צריך את הType):

1
2
3
4
5
6
7
MethodInfo formatInfo =
typeof (string).GetMethod("Format", new Type[] {typeof(string), typeof (object[])});
Func<string, object[], string> format =
(Func<string, object[], string>)
Delegate.CreateDelegate
(typeof(Func<string, object[], string>), formatInfo);

אם אנחנו רוצים לקרוא לפונקציה של instance, ניתן לעשות זאת כך:

1
2
3
4
5
6
7
8
9
10
11
12
MethodInfo replaceInfo =
typeof (string).GetMethod("Replace", new Type[] {typeof (string), typeof (string)});
Func<string, string, string, string> replace =
(Func<string, string, string, string>)
Delegate.CreateDelegate(typeof (Func<string, string,string, string>), replaceInfo);
for (int i = 0; i < 10000; i++)
{
Console.WriteLine(replace(i.ToString(), "0", ""));
//Console.WriteLine(i.ToString().Replace("0", ""));
}

(הקוד מסיר אפסים מההצגה העשרונית של מספר)

קצת מפחיד, אבל מה שקורה כאן זה שאנחנו יוצרים delegate שמקבל בתור הפרמטר הראשון את הinstance שעליו אנחנו רוצים להריץ את המתודה, ושאר הפרמטרים הם הפרמטרים של המתודה. (כמו בטיפ 148 בקריאה לInvoke).


בנוסף, אם אנחנו כל הזמן ניגשים לאותו Instance, ניתן ליצור delegate שיפעל תמיד על הinstance ההוא. למשל:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
MethodInfo containsInfo =
typeof (string).GetMethod("Contains", new Type[] {typeof (string)});
object theNumbers = "The numbers are 4,8,15,16,23,42";
Func<string, bool> contains =
(Func<string, bool>)
Delegate.CreateDelegate(typeof (Func<string, bool>),
theNumbers,
containsInfo);
for (int i = 0; i < 10000; i++)
{
if (contains(i.ToString()))
//if (theNumbers.Contains(i.ToString()))
{
Console.WriteLine(i);
}
}

למה זה יותר מהיר? מה שקורה זה שהDynamic Binding קורה רק פעם אחת. מה ז"א?

כשאנחנו קוראים לInvoke של MethodInfo מפוענחת איך תתבצע הקריאה – כלומר לאיזו חתימה אנחנו מצפים לקרוא, ואיך לשלוח את הפרמטרים (הרי הטיפוס שאנחנו שולחים לפונקציה לא חייב להיות זהה לטיפוס שהמתודה מצפה לקבל – הוא יכול גם לרשת ממנו וכו’) ואיך להחזיר את ערך ההחזר.

מיפוי זה מבזבז את רוב הזמן בקריאה לפונקציה.

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

חג פסח שמח וחופש נעים!

שתף