Consider the following C# code where an event is subscribed and then triggered. What will be printed to the console?
using System; class Publisher { public event Action OnChange; public void Raise() { OnChange?.Invoke(); } } class Subscriber { public void Subscribe(Publisher p) { p.OnChange += () => Console.WriteLine("Event triggered"); } } class Program { static void Main() { Publisher pub = new Publisher(); Subscriber sub = new Subscriber(); sub.Subscribe(pub); pub.Raise(); } }
Think about what happens when the event is raised after subscription.
The subscriber adds a handler to the event. When Raise() is called, the event triggers and prints "Event triggered".
Given the code below, what will be the output after GC.Collect() is called?
using System; class Publisher { public event Action OnChange; public void Raise() { OnChange?.Invoke(); } } class Subscriber { public Subscriber(Publisher p) { p.OnChange += Handler; } void Handler() { Console.WriteLine("Handled event"); } ~Subscriber() { Console.WriteLine("Subscriber finalized"); } } class Program { static void Main() { Publisher pub = new Publisher(); Subscriber sub = new Subscriber(pub); sub = null; GC.Collect(); GC.WaitForPendingFinalizers(); pub.Raise(); } }
Think about whether the subscriber object is collected by the garbage collector.
The subscriber is still referenced by the publisher's event delegate, so it is not garbage collected. The finalizer does not run, and the event handler runs printing "Handled event".
Examine the code below. The developer tries to unsubscribe the event to avoid memory leaks but it doesn't work. Why?
using System; class Publisher { public event Action OnChange; public void Raise() => OnChange?.Invoke(); } class Subscriber { private Publisher _pub; public Subscriber(Publisher pub) { _pub = pub; _pub.OnChange += Handler; } public void Unsubscribe() { _pub.OnChange -= () => Console.WriteLine("Handled"); } private void Handler() { Console.WriteLine("Handled"); } } class Program { static void Main() { Publisher pub = new Publisher(); Subscriber sub = new Subscriber(pub); sub.Unsubscribe(); pub.Raise(); } }
Consider how delegates and lambdas work in C# when subscribing and unsubscribing.
Unsubscription requires the exact same delegate instance. The lambda in Unsubscribe() is a new delegate, so it does not remove the original subscription, causing the event to still fire.
Consider this code using a weak event pattern to avoid memory leaks. What will be printed?
using System; using System.Runtime.CompilerServices; class Publisher { public event Action OnChange; public void Raise() => OnChange?.Invoke(); } class Subscriber { public Subscriber(Publisher pub) { WeakReference<Subscriber> weakRef = new WeakReference<Subscriber>(this); pub.OnChange += () => { if (weakRef.TryGetTarget(out var target)) { target.Handler(); } }; } public void Handler() { Console.WriteLine("Handled via weak ref"); } ~Subscriber() { Console.WriteLine("Subscriber finalized"); } } class Program { static void Main() { Publisher pub = new Publisher(); Subscriber sub = new Subscriber(pub); sub = null; GC.Collect(); GC.WaitForPendingFinalizers(); pub.Raise(); } }
Think about whether the subscriber is still alive when the event is raised.
The weak reference does not keep the subscriber alive. After GC, the subscriber is finalized and the event handler does not run because the target is gone.
Choose the best explanation for why unsubscribing from events is important to prevent memory leaks in C#.
Consider how event delegates hold references internally.
When a subscriber subscribes to an event, the publisher holds a strong reference to the subscriber delegate, preventing the subscriber from being garbage collected until unsubscribed.