Question

Will a class be garbage collected if it is subscribed to some event but does nothing with it?

Consider the following class which subscribes to an event but does nothing with it:

public class Class1
{
    public Class1()
    {
        SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged;
    }

    void SystemEvents_PowerModeChanged(object sender, PowerModeChangedEventArgs e)
    {
            
    }
}

With an instance of said class created like this:

public class Runner
{
    Class1 class1 = new Class1();   
}

On the one hand, class1 is subscribed to an event, on the other, the runtime can easily deduce that there won't be any effect by it.

So, will class1 be eligible for garbage collection?

 4  114  4
1 Jan 1970

Solution

 4

In .NET, the garbage collector (GC) cleans up objects that aren’t being used anymore. However, if an object is subscribed to an event, it’s a bit trickier.

When you subscribe to an event, your object (the subscriber) gets added to the event’s delegate list. This means the event publisher holds a reference to your object. As long as this reference exists, your object won’t be garbage collected, even if you set all other references to it to null.

Here’s a quick example to show what I mean:

public class Publisher
{
    public event EventHandler SomeEvent;

    public void RaiseEvent()
    {
        SomeEvent?.Invoke(this, EventArgs.Empty);
    }
}

public class Subscriber
{
    public Subscriber(Publisher publisher)
    {
        publisher.SomeEvent += HandleEvent;
    }

    private void HandleEvent(object sender, EventArgs e)
    {
        // Handle the event
    }
}

public static void Main()
{
    Publisher publisher = new Publisher();
    Subscriber subscriber = new Subscriber(publisher);

    // Even after setting this to null, subscriber is still referenced by the event
    subscriber = null;

    // Subscriber won't be garbage collected as long as publisher is alive
}

In this example, even after setting the subscriber to null, the Subscriber object won’t be garbage collected because the Publisher object still holds a reference to it through the event.

You need to unsubscribe from the event when you’re done with the subscriber. Here’s one way to do it:

public class Subscriber : IDisposable
{
    private Publisher _publisher;

    public Subscriber(Publisher publisher)
    {
        _publisher = publisher;
        _publisher.SomeEvent += HandleEvent;
    }

    private void HandleEvent(object sender, EventArgs e)
    {
        // Handle the event
    }

    public void Dispose()
    {
        if (_publisher != null)
        {
            _publisher.SomeEvent -= HandleEvent;
            _publisher = null;
        }
    }
}

In this version, the Subscriber class implements IDisposable and unsubscribes from the event when disposed of. This way, the reference is removed, and the Subscriber object can be garbage collected.

So, to answer your question: no, a class won’t be garbage collected if it’s only referenced by an event subscription. You need to manually unsubscribe from the event to allow the GC to clean it up.

2024-07-14
Weggo

Solution

 2

No, it won't be garbage-collected because the static event acts as a root for the instance whose method is registered in the event delegate list.

If we want to demonstrate this, we can test by making use of the WeakReference class that keeps a weak reference to an instance that does not keep the object alive. It's nice to track the reachability/collectability of an instance.

So, the demo that needs to be compiled in Release mode and if you run on later .NET versions (also add <TieredCompilation>false</TieredCompilation> to .csproj) :

class A {
    public A() {
        StaticEventClass.StaticEvent += OnStaticEvent;
    }
    public void OnStaticEvent(object sender, EventArgs e) {
        Console.WriteLine("Static event triggered");
    }

}

public class StaticEventClass {
    public static event EventHandler StaticEvent;
}

[MethodImpl(MethodImplOptions.AggressiveOptimization)]
static void Main() {
    var a = new A();
    var wr = new WeakReference(a);
    a = null;
    GC.Collect();
    GC.WaitForPendingFinalizers();
    GC.Collect();

    Console.WriteLine(wr.IsAlive); // True
}

Comment/Delete the event-subscription in the constructor and the outcome is False.

You can really see how the event has a reference to our object if you decompile the constructor of A and specifically the event-subscription part:

    ...
    IL_0006: ldarg.0 // this - our instance
    IL_0007: ldftn instance void A::OnStaticEvent(object, class [System.Runtime]System.EventArgs)
    IL_000d: newobj instance void [System.Runtime]System.EventHandler::.ctor(object, native int)
    IL_0012: call void StaticEventClass::add_StaticEvent(class [System.Runtime]System.EventHandler)

In the third line we construct an instance of the EventHandler delegate that is used to "subscribe" to the event, i.e. added to the list of delegates maintained by the event.

newobj instance void [System.Runtime]System.EventHandler::.ctor(object, native int)

It takes two parameters which are given in the previous two lines:

  1. ldarg.0 which is the this instance of A
  2. ldftn gives us an unmanaged pointer to the OnStaticEvent instance method we are registering - native int

It is this delegate instance that keeps our object directly alive. Because it keeps a reference to the object it got through its constructor as an internal field - from source code of Delegate class.

public abstract partial class Delegate : ICloneable, ISerializable
{
    // _target is the object we will invoke on
    internal object? _target; // Initialized by VM as needed; null if static delegate

The static event just has a list of such delegate instances that the GC checks to determine the reachability of objects and their eligibility for garbage collection.

2024-07-15
Ivan Petrov