205. Solving the problem with events - part 4

ראינו בפעמים הקודמות כיצד ניתן להמנע מדליפות זכרון של הרשמות לEventים באמצעות מימוש של Weak Event.

אמרתי שעדיין יש בעיה – האובייקט שנרשם לאירוע אמנם כבר לא דולף, אבל הWeakEventHandlerים דולפים. הסיבה לכך היא שהEvent עדיין מחזיק Reference לWeakEventHandler ולכן הGarbage Collector לא אוסף אותו. (כמו בטיפ 201)

אז איך אפשר לפתור את הבעיה? פתרון אפשרי הוא להעביר לWeakEventHandler איזשהו Delegate שיבצע הסרת רישום מהEvent ברגע שהאובייקט מת:

אם נחזור למימושים מאתמול, ניצור Delegate כזה:

1
public delegate void UnregisterDelegate(EventHandler<TEventArgs> subscription);

נקבל אותו בConstructor:

1
protected WeakEventHandler(EventHandler<TEventArgs> handler, UnregisterDelegate unregisterHandler)

נחזיק אותו:

1
protected readonly UnregisterDelegate mUnregisterHandler;

נאתחל אותו בConstructor:

1
mUnregisterHandler = unregisterHandler;

כעת ברגע שהאובייקט שלנו מת, נקרא לפונקציה הזו, אשר תסיר את הרישום שלנו מהDelegate:

1
2
3
4
5
6
7
8
9
10
11
12
13
public void Invoke(object sender, TEventArgs eventArgs)
{
object target = mTarget.Target;
if (target != null)
{
InnerInvoke(target, sender, eventArgs);
}
else
{
mUnregisterHandler(this);
}
}

נשנה כמובן גם בConstructorים של הבנים את החתימה:

1
2
3
public RuntimeWeakEventHandler(EventHandler<TEventArgs> handler,
UnregisterDelegate unregisterHandler)
: base(handler, unregisterHandler)

כעת איך משתמשים?

ברישום לEvent במקום לכתוב כמו שכתבנו עד עכשיו:

1
source.EventRaised += new RuntimeWeakEventHandler<EventArgs>(OnEventRaised);

נכתוב ככה:

1
source.EventRaised += new RuntimeWeakEventHandler<EventArgs>(OnEventRaised, x => source.EventRaised -= x);

מה שבעצם קורה זה שברגע שהאובייקט שלנו מת, אנחנו יודעים לבטל את הרישום של הWeakEventHandler לEvent. אז מה שקורה זה שאין הצבעות לWeakEventHandler ולכן הGarbage Collector יכול לאסוף אותו.

מאחר והסינטקס לא כל כך להיט, אפשר ליצור Extension Method שיהפוך אותו לטיפה יותר יפה (ראו גם טיפים מספר 68-69):

1
2
3
4
5
6
7
public static RuntimeWeakEventHandler<TEventArgs> MakeWeak<TEventArgs>
(this EventHandler<TEventArgs> eventHandler,
WeakEventHandler<TEventArgs>.UnregisterDelegate unregisterDelegate)
where TEventArgs : EventArgs
{
return new RuntimeWeakEventHandler<TEventArgs>(eventHandler, unregisterDelegate);
}

ואז להשתמש בסינטקס הזה:

1
source.EventRaised += new EventHandler<EventArgs>(OnEventRaised).MakeWeak(x => source.EventRaised -= x);

שזה די סביר.

בהמשך נראה שיפור נוסף שאפשר לעשות בסינטקס,

בינתיים שיהיה סופ"ש מלא אירועים לא חלשים טוב.

שתף