How to Clear Event Subscriptions in C#

How can I clear event subscriptions in C#?

From within the class, you can set the (hidden) variable to null. A null reference is the canonical way of representing an empty invocation list, effectively.

From outside the class, you can't do this - events basically expose "subscribe" and "unsubscribe" and that's it.

It's worth being aware of what field-like events are actually doing - they're creating a variable and an event at the same time. Within the class, you end up referencing the variable. From outside, you reference the event.

See my article on events and delegates for more information.

How to remove all event handlers from an event

I found a solution on the MSDN forums. The sample code below will remove all Click events from button1.

public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();

button1.Click += button1_Click;
button1.Click += button1_Click2;
button2.Click += button2_Click;
}

private void button1_Click(object sender, EventArgs e) => MessageBox.Show("Hello");
private void button1_Click2(object sender, EventArgs e) => MessageBox.Show("World");
private void button2_Click(object sender, EventArgs e) => RemoveClickEvent(button1);

private void RemoveClickEvent(Button b)
{
FieldInfo f1 = typeof(Control).GetField("EventClick",
BindingFlags.Static | BindingFlags.NonPublic);

object obj = f1.GetValue(b);
PropertyInfo pi = b.GetType().GetProperty("Events",
BindingFlags.NonPublic | BindingFlags.Instance);

EventHandlerList list = (EventHandlerList)pi.GetValue(b, null);
list.RemoveHandler(obj, list[obj]);
}
}

How to remove all eventhandler

Simply set the event to null:

this.Something = null;

It will unregister all event handlers.

How can I clear event subscriptions in C++/CLI?

C++/CLI hides underlying backing store (the delegate) even within the class so you can't simply set it to nullptr. Because you can't rely on default event implementation then you have to do it by yourself:

private: EventHandler^ _myEvent;

public: event EventHandler^ MyEvent
{
void add(EventHandler^ handler)
{
_myEvent += handler;
}

void remove(EventHandler^ handler)
{
_myEvent -= handler;
}
}

Now you can simply nullify myEvent delegate:

_myEvent = nullptr;

This, of course, will change how you'll invoke it too (same as C# instead of C++/CLI short version):

EventHandler^ myEvent = _myEvent;
if (myEvent != nullptr)
myEvent(this, e);

Do I need to remove event subscriptions from objects before they are orphaned?

Yes you do. The event publishers are holding references to the objects, and would prevent them from being garbage collected.

Let's look at an example to see what happens. We have two classes; one exposes an event, the other consumes it:

class ClassA
{
public event EventHandler Test;
~ClassA()
{
Console.WriteLine("A being collected");
}
}
class ClassB
{
public ClassB(ClassA instance)
{
instance.Test += new EventHandler(instance_Test);
}

~ClassB()
{
Console.WriteLine("B being collected");
}

void instance_Test(object sender, EventArgs e)
{
// this space is intentionally left blank
}
}

Note how ClassB does not store a reference to the ClassA instance; it merely hooks up an event handler.

Now, let's see how the objects are collected. Scenario 1:

ClassB temp = new ClassB(new ClassA());
Console.WriteLine("Collect 1");
GC.Collect();
Console.ReadKey();
temp = null;
Console.WriteLine("Collect 2");
GC.Collect();
Console.ReadKey();

We create a ClassB instance and hold a reference to it through the temp variable. It gets passed a new instance of ClassA, where we do not store a reference to it anywhere, so it goes out of scope immediately after the ClassB constructor is done. We have the garbage collector run once when ClassA has gone out of scope, and once when ClassB as gone out of scope. The output:

Collect 1
A being collected
Collect 2
B being collected

Scenario 2:

ClassA temp = new ClassA();
ClassB temp2 = new ClassB(temp);
temp2 = null;
Console.WriteLine("Collect 1");
GC.Collect();
Console.ReadKey();
temp = null;
Console.WriteLine("Collect 2");
GC.Collect();
Console.ReadKey();

A new instance of ClassA is created and a reference to it is stored in the temp variable. Then a new instance of ClassB is created, getting the ClassA instance in temp passed to it, and we store a reference to it in temp2. Then we set temp2 to null, making the ClassB instance going out of scope. As before, we have the garbage collector run after each instance has gone out of scope. The output:

Collect 1
Collect 2
B being collected
A being collected

So, to conclude; if the instance that exposes an event goes out of scope, it becomes available for garbage collection, regardless of whether there are event handlers hooked up or not. If an instance that has an event handler hooked up to an event in another instance, it will not be available for garbage collection until either the event handler is detached, or the instance to which the event handler is attached becomes available for garbage collection.

How do I unsubscribe all handlers from an event for a particular class in C#?

Each delegate has a method named GetInvocationList() that returns all the actual delegates that have been registered. So, assuming the delegate Type (or event) is named say MyDelegate, and the handler instance variable is named myDlgHandler, you can write:

Delegate[] clientList = myDlgHandler.GetInvocationList();
foreach (var d in clientList)
myDlgHandler -= (d as MyDelegate);

to cover the case where it might be null,

 if(myDlgHandler != null)
foreach (var d in myDlgHandler.GetInvocationList())
myDlgHandler -= (d as MyDelegate);

How to clear all static event handlers in a class via reflection?

In general, it doesn't make sense to "remove all subscribers" of an event in .NET. Events don't even need to remember who their subscribers are! An event really is just a pair of methods, called add and remove, that take some type of delegate as parameters, and you can do whatever you want in those methods, e.g.

public static event Action<int> MySillyEvent {
add {
Console.WriteLine($"{value.Method.Name} is being added!");
}
remove {
Console.WriteLine($"{value.Method.Name} is being removed!");
}
}

Now tell me, what would "remove all subscribers from MySillyEvent" even mean?

If you limit your scope, to just field-like events, which are events that are backed by a delegate field, then this is a more tractable problem. You just need to set the delegate field that's backing the event to null. Your notifyA and notifyB events are field-like events.

However, the names of the delegate fields are implementation-dependent, and EventInfo doesn't know whether it is field-like or not.

If your class doesn't have any explicitly declared static delegate fields, you can just find all the non-public, static fields of a delegate type in your class:

var fields = typeof(ClientEvents).GetFields(BindingFlags.Static | BindingFlags.NonPublic)
.Where(x => x.FieldType.BaseType.IsSubclassOf(typeof(Delegate)));
foreach (var field in fields) {
field.SetValue(null, null);
}

Otherwise, you need to rely on some implementation details, like this answer does. We assume that the field name is the same as the event's name. This is at least true with the compiler that I'm using.

var type = typeof(ClientEvents);
var fields = type.GetEvents(BindingFlags.Static | BindingFlags.Public)
.Select(e => type.GetField(e.Name, BindingFlags.Static | BindingFlags.NonPublic));
foreach (var field in fields) {
field.SetValue(null, null);
}

Is it possible to unsubscribe from multiple events with a single method call?

You can use the following pattern. First create class which will run arbitrary function you pass on construction and another on dispose:

public class EventSubscription : IDisposable {
private readonly Action _unsubscribe;

private EventSubscription(Action subscribe, Action unsubscribe) {
_unsubscribe = unsubscribe;
subscribe();
}

public static IDisposable Create(Action subscribe, Action unsubscribe) {
return new EventSubscription(subscribe, unsubscribe);
}

public void Dispose() {
_unsubscribe();
}
}

Then create field in your class where you subscribe to events:

private static readonly List<IDisposable> _subscriptions = new List<IDisposable>();

And subscribe like this:

_subscriptions.Add(EventSubscription.Create(
() => Event1 += OnEvent1,
() => Event1 -= OnEvent1));
_subscriptions.Add(EventSubscription.Create(
() => Event2 += OnEvent2,
() => Event2 -= OnEvent2));

When you need to unsubsribe, just do:

_subscriptions.ForEach(c => c.Dispose());

Advantages include unsubscribing from all at once, and much less chance to forget to unsubscribe, because you always pass += and -= handler in pair, in the same call.

Variation of this is this general purpose class:

public class Disposable : IDisposable {
private readonly Action _action;
private Disposable(Action action) {
_action = action;
}
public static IDisposable FromAction(Action action) {
return new Disposable(action);
}
public void Dispose() {
_action();
}
}

You can find it already in many places (like .NET reactive extensions), but if not - you can implement yourself. Then above code becomes:

Event1 += OnEvent1;
_subscriptions.Add(Disposable.FromAction(() => Event1 -= OnEvent1));

Using IDisposable is not necessary of course, you can just have list of Actions.



Related Topics



Leave a reply



Submit