How to Get the Subscribers of an Event

How do I get the subscribers of an event?

C# events/delegates are multicast, so the delegate is itself a list. From within the class, to get individual callers, you can use:

if (field != null) 
{
// or the event-name for field-like events
// or your own event-type in place of EventHandler
foreach(EventHandler subscriber in field.GetInvocationList())
{
// etc
}
}

However, to assign all at once, just use += or direct assignment:

SomeType other = ...
other.SomeEvent += localEvent;

How to find all methods currently subscribed to an event in WPF / C# during debugging in Visual Studio

In most cases, Find All References should have it covered, but this fails when the event is not unique enough (imagine Button.Click).

You can access this in a debugger by browsing to the event object and examining the _invocationList field. If this field is not populated, look at _methodPtr field. If both fields are null, then no one is subscribed.

_target is the object containing the subscribed method. If it is null, a static method is subscribed (which makes identification much more tricky). Otherwise, you can dump the method table of the target object to find the subscribed method.

In Visual studio, the debug tooltips make this easy. For a unicast delegate, hovering over the event shows the declaring type and method name (and arity if needed):

screencap showing debugger tooltip

for multicast, the _invocationList takes over:

screencap showing debugger tooltip

How to call all event handler subscribers and get their result?

You can call EventName.GetInvocationList() to get all the delegates subscribed to the event.

In that way, you can call each delegate and return the result, in whatever way you want.

foreach (Comparer c in OnComparaison.GetInvocationList())
{
int result = c(0, 9);
}

This way, you can get all results for example:

IEnumerable<int> allValues = OnComparaison
.GetInvocationList()
.Select(x => ((Comparer)x)(0, 9));

How to easilly see number of event subscriptions while debugging?

You will have to use Reflection to get to the invocation list of the event delegate:

    textBox1.TextChanged += textBox1_TextChanged;
MessageBox.Show("asdf");
textBox1.TextChanged -= textBox1_TextChanged;
textBox1.Text = DateTime.Now.ToString();
textBox1.TextChanged += textBox1_TextChanged;
var eventField = textBox1.GetType().GetField("TextChanged", BindingFlags.GetField
| BindingFlags.NonPublic
| BindingFlags.Instance);

var subscriberCount = ((EventHandler)eventField.GetValue(textBox1))
.GetInvocationList().Length;

How do I retrieve the Subscriber List for an Event at runtime?

The whole point of an event it defines (only) an add/remove API (like how a property defines get/set).

The entire point of an event is that externally you can't do this (access the subscribers). I'm guessing that in the "clock" example, the code that accesses the list is inside the type that declares the event; that is fine: inside the type, you have full access to the backing implementation (often a delegate field).

Externally, you should only care about your own handlers, that you already know about because you subscribed them. Attempts to fetch the backer exist, but it is brittle and not recommended. In this case, it uses an EventHandlerList, for example.

Why do you need this? It usually means that you are doing something wrong (sorry, but it does).

How can I track subscribers to an event in C#?

If you have access to the actual delegate (if you're using the shorthand event syntax, then this is only within the actual declaring class, as the delegate is private), then you can call GetInvocationList().

For instance:

public event EventHandler MyEvent;

To get the list of subscribers, you can call:

Delegate[] subscribers = MyEvent.GetInvocationList();

You can then inspect the Method and Target properties of each element of the subscribers array, if necessary.

The reason this works is because declaring the event as we did above actually does something akin to this:

private EventHandler myEventDelegate;

public event EventHandler MyEvent
{
add { myEventDelegate += value; }
remove { myEventDelegate -= value; }
}

This is why the event looks different when viewed from within the declaring class compared to anywhere else (including classes that inherit from it). The only public-facing interface is the add and remove functionality; the actual delegate, which is what holds the subscriptions, is private.

How to keep track of event subscriptions for various events

Short answer: this is impossible. You can't store events in collections and clear their handler lists later.

Events serve only one purpose - encapsulation. The only thing they do is provide accessors (namely add and remove) so code outside your class can only add/remove handlers to a backing delegate field. These pieces of code are basically same:

class MyClass
{
public event EventHandler MyEvent;
}
class MyClass
{
private EventHandler myDelegate;

public event EventHandler MyEvent
{
add => myDelegate += value;
remove => myDelegate -= value;
}
}

But suppose we don't use events, and use delegates directly. You could create a dictionary where the keys are delegates instead of events, but that wouldn't work for your problem. This is because delegates are immutable. You can't store a delegate in a collection and then retrieve it and clear its invokation list.

The only solution here would be to reference every event directly, like in this code. I'm not sure whether this solution will work for you.

using System;
using System.Threading;
using System.Windows.Forms;

namespace ConsoleApp3
{
class Program
{
static void Main()
{
var ticker = new Ticker();
var form = new Form();
form.Show();

form.FormClosing += (s, e) => ticker.ClearSubscriptions();

ticker.Ticked += new EventHandler((s, e) => form.Invoke(
new Action(() => form.Text = DateTime.Now.ToString())));

Application.Run();
}

class Ticker
{
public event EventHandler Ticked;

public Ticker()
{
new Thread(new ThreadStart(() => {
while (true)
{
Ticked?.Invoke(this, EventArgs.Empty);
Thread.Sleep(1000);
}
})).Start();
}

public void ClearSubscriptions()
{
Ticked = null;
}
}
}
}

As you can see, ClearSubscriptions clears the Ticked event manually. If you have more events, you must also clear them manually and only in the Ticker class, because it's the only place that has access to the underlying delegate. You can only clear the events you've declared yourself.

Alternatively, you could store a separate list for each event.

static void Main()
{
var ticker = new Ticker();
var form = new Form();
form.Show();

var tickedSubscriptions = new List<EventHandler>();

form.FormClosing += (s, e) =>
{
foreach (var subscription in tickedSubscriptions)
{
ticker.Ticked -= subscription;
}

tickedSubscriptions.Clear();
};

var handler = new EventHandler((s, e) => form.Invoke(
new Action(() => form.Text = DateTime.Now.ToString())));

tickedSubscriptions.Add(handler);
ticker.Ticked += handler;

Application.Run();
}

But in my opinion, this solution is less than ideal, because you have to keep track of many separate lists.

UPDATE:

I've thought of another solution which works for your case. I'm not sure whether it's elegant though.

Even though delegates are immutable, nothing prevents us from creating wrapper objects that can change the backing delegate and put these wrappers into the dictionary.

using System;
using System.Collections.Generic;
using System.Threading;
using System.Windows.Forms;

namespace ConsoleApp3
{
class Program
{
private static Dictionary<EventHandlerWrapper, List<EventHandler>> subscriptions =
new Dictionary<EventHandlerWrapper, List<EventHandler>>();

static void Main()
{
var ticker = new Ticker();
var form = new Form();
form.Show();

form.FormClosing += (s, e) =>
{
foreach (var subscription in subscriptions)
{
foreach (var handler in subscription.Value)
{
subscription.Key.Remove(handler);
}
}

subscriptions.Clear();
};

var updateTitle = new EventHandler((s, e) =>
form.Invoke(new Action(() => form.Text = DateTime.Now.ToString())));

ticker.Ticked += updateTitle;
subscriptions.Add(ticker.TickedWrapper, new List<EventHandler> { updateTitle });

Application.Run();
}

class Ticker
{
public event EventHandler Ticked;
public EventHandlerWrapper TickedWrapper;

public Ticker()
{
TickedWrapper = new EventHandlerWrapper(
() => Ticked,
handler => Ticked += handler,
handler => Ticked -= handler);

new Thread(new ThreadStart(() => {
while (true)
{
Ticked?.Invoke(this, EventArgs.Empty);
Thread.Sleep(1000);
}
})).Start();
}
}

class EventHandlerWrapper
{
public Func<EventHandler> Get { get; }
public Action<EventHandler> Add { get; }
public Action<EventHandler> Remove { get; }

public EventHandlerWrapper(
Func<EventHandler> get,
Action<EventHandler> add,
Action<EventHandler> remove)
{
this.Get = get;
this.Add = add;
this.Remove = remove;
}
}
}
}

How to access subscribers to PropertyChanged event?

How can I access the subscribers to PropertyChanged?

You don't, basically. An event only supports subscribe and unsubscribe functionality.

I've researched a bit and found that this should be possible calling GetInvocationList() and counting the elements in the returned array.

That assumes you can get at the underlying delegate field - if there even is one. There might not be - there are lots of ways an event can be implemented, just like there are lots of ways a property can be implemented.

Basically, what you're asking for breaks the encapsulation model of events. While there are ways around this in certain situations using reflection, you should be aware that you're fighting the design of the system.

See my article on events and delegates for more about the differences between the two.



Related Topics



Leave a reply



Submit