202. Solving the problem with events - part 1

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

ראינו גם שאפשר למנוע את הדליפה ע”י קריאה לפונקציה שמבטלת את ההרשמה לאירועים רגע לפני שהאובייקט עוזב.

בסדרת הפוסטים הקרובה נראה כיצד אפשר לפתור את הבעיה בצורה אחרת…

השיטה היא באמצעות שימוש בטיפוס שנקרא WeakReference.

מה זה בדיוק WeakReference?

WeakReference היא טיפוס המצביע לReference כלשהו, אלא שבניגוד לשימוש רגיל בobject, מופע של WeakReference אינו נספר בתור Reference לאובייקט מבחינת הGarbage Collector. לכן, אם לאף אחד חוץ מלWeakReference אין Reference לאובייקט מסוים, האובייקט ייאסף ע”י הGarbage Collector.

נשמע טוב, לא?


אז מה הקשר לEventים?

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

מה שאנחנו יכולים לעשות זה ליצור מעין EventHandler משלנו בו הTarget יהיה WeakReference ואז איכשהו להפעיל את המתודה שלו.

ככה הReference לאובייקט לא יספר בספירה של הGarbage Collector, ולכן יאסף.

אז נתחיל:

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

1
2
3
4
5
6
7
8
public class WeakEventHandler<TEventArgs>
where TEventArgs : EventArgs
{
public WeakEventHandler(EventHandler<TEventArgs> handler)
{
// ...
}
}

זאת כבר התחלה טובה, כי קוד כזה יתקמפל:

1
2
WeakEventHandler<EventArgs> myEvent =
new WeakEventHandler<EventArgs>(EventRaised);

למרבה הצער, אנחנו לא יכולים לרשת מ"הטיפוסים המיוחדים" ששמם Delegate, MulticastDelegate וEventHandler<TEventArgs>, או מכל Delegate אחר.

לכן נצטרך למצוא איזשהו Workaround, שהרי היינו רוצים שהשורה הבאה תתקמפל:

1
2
EventHandler<EventArgs> myEvent =
new WeakEventHandler<EventArgs>(EventRaised);

הפתרון, למקרה שלא ניחשתם הוא להשתמש בimplict cast:

1
2
3
4
public static implicit operator EventHandler<TEventArgs>(WeakEventHandler<TEventArgs> source)
{
return null;
}

כמובן, צריך לשים פה מימוש מתאים, שהרי ככה זה לא יעבוד 😃

בואו נדבר על המימוש:

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

כשמפעילים את הEvent שלנו אנחנו מעוניינים לבדוק האם הReference של האובייקט עדיין קיים, ובמידה וכן, להפעיל את הפונקציה שלו.

מה שנעשה זה ניצור פונקציה אליה יכנסו בכל קריאה לWeak-event שלנו. בה אנחנו נבצע את הבדיקה וכו’:

1
2
3
4
5
public void Invoke(objectsender, TEventArgs e)
{
// TODO: check here if the reference is still alive
// TODO: and call its method.
}

ואז בהסבה לEventHandler<TEventArgs>ניצור פשוט Delegate לפונקציה זו:

1
2
3
4
public static implicit operator EventHandler<TEventArgs>(WeakEventHandler<TEventArgs> source)
{
return new EventHandler<TEventArgs>(source.Invoke);
}

עכשיו מה שיקרה זה שכל פעם שיקפיצו את הWeak-event שלנו, תקרא הפונקציה Invoke. אם למשל נכתוב בה קוד כזה:

1
2
3
4
public void Invoke(object sender, TEventArgs e)
{
Console.WriteLine(DateTime.Now);
}

אז אם נרשם לאיזשהו Event ככה:

1
2
subject.EventRaised +=
new WeakEventHandler<EventArgs>(EventRaised);

אז כשיוקפץ הEvent הנ"ל, נכנס לפונקציה Invoke ולכן יכתבו למסך התאריך והשעה.

בהמשך נראה מה בדיוק נכתוב בפונקציה Invoke ואיך זה מתקשר לאותם WeakReferenceים.

המשך יום מלא אירועים לא חלשים טוב

שתף