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:
ldarg.0
which is the this
instance of A
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.