349. DynamicProxy

אינו פעם שעברה איך ניתן לממש Proxy כלשהו בעזרת RealProxy של הFramework.

למרבה הצער, ראינו שזה מלווה בקריאות איטיות מאוד למתודות. (פי 500 יותר איטיות בערך)

קיימת עוד דרך לממש Proxy דינאמי והיא באמצעות Reflection.Emit.

Reflection.Emit היא בעצם ספריה המאפשרת לנו לקמפל קוד בזמן ריצה. הדבר הזה כולל בין השאר את הדברים הבאים:

  • יצירת מתודות בזמן ריצה - זה קשור לקימפול Expressionים בזמן ריצה. למעשה Expressionים משתמשים בReflection.Emit כדי להתקמפל. ראו גם טיפים על Expressionים: טיפים מספר 173-181.
  • יצירת טיפוסים בזמן ריצה.

די קשה להשתמש בReflection.Emit, כי שימוש בזה מצריך כתיבת IL שכנראה רובנו לא דוברים אותה באופן שוטף.

עם זאת, השימוש בReflection.Emit מאפשר לעשות דברים נפלאים.

למשל, נוכל ליצור טיפוס בזמן ריצה שמממש איזשהו ממשק ומפנה כל קריאה אליו לאיזשהו Delegate.

כך בעצם נוכל לממש Proxy בעצמנו בצורה דינאמית.

מאחר ואני לא מבין חובבי כתיבת IL, אני לא אראה כאן איך כותבים דבר כזה.

במקום זאת, אני אספר על איזשהו פרויקט Open source שנקרא DynamicProxy של Castle. זוהי ספריה שעושה את כל התהליך שדיברתי עליו למעלה:

היא יודעת ליצור טיפוס המממש ממשק בצורה של Proxy, כלומר לקרוא למתודה מתאימה בכל קריאה למתודה של הממשק.

איך משתמשים בזה? זה די פשוט: צריך לממש את הממשק ששמו IInterceptor, שם נכתוב מה אנחנו רוצים לעשות כשתקרא מתודה כלשהי (זו בעצם הפונקציה שעושים אליה Proxy, בדומה לInvoke שראינו פעם שעברה):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ConsoleWriterInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
ParameterInfo[] parameters = invocation.Method.GetParameters();
for (int i = 0; i < parameters.Length; i++)
{
Console.WriteLine("{0} := {1}",
parameters[i].Name,
invocation.Arguments[i]);
}
}
}

הממשק IInvocation נותן לנו גישה לכל המידע של הפונקציה: הפרמטרים איתם נקראה, הMethodInfo וכו’ (ראו גם טיפ מספר 345).

בנוסף, אנחנו יכולים לקבוע בו עוד דברים, כמו ערכי ההחזר של הפונקציה (כולל פרמטרים שהם out וref), הException שהפונקציה מחזירה וכו’.

כעת כדי להשתמש בIInterceptor שכתבנו כדי ליצור Proxy משלנו:

1
2
3
4
5
6
7
8
9
10
11
12
13
ProxyGenerator generator = new ProxyGenerator();
IBank bank =
generator.CreateInterfaceProxyWithoutTarget<IBank>
(new ConsoleWriterInterceptor());
bank.Deposit("stam", 100);
//id := stam
//amount := 100
bank.Deposit("yossi", 1024);
//id := yossi
//amount := 1024

די מגניב!


מה לגבי ביצועים? ראינו פעם שעברה שקריאה לProxy ריק עלתה פי 500 מקריאה רגילה למתודה.

כאן המצב יותר טוב. אם נערוך השוואה כמו פעם שעברה:

Number of calls Dummy (in milliseconds) DynamicProxy (in milliseconds)
10 0 0
100 0 0
1000 0 0
10000 2 2
100000 1 15
1000000 9 151
10000000 111 1777
100000000 1073 16896
1000000000 9727 174955

כפי שאנחנו רואים, גם כאן קריאה ישירה למתודה מהירה יותר מקריאה דרך DynamicProxy, אבל הפעם רק פי 17 יותר מהיר (ולא פי 500 כמו פעם שעברה).

שוב שימו לב למספר הקריאות שצריך כדי להרגיש בהבדל.

המשך יום מיופה כוח דינאמי טוב.

שתף