How to determine if an event is already subscribed
C#.NetC# Problem Overview
In my .NET application I am subscribing to events from another class. The subscription is conditional. I am subscribing to events when the control is visible and de-subscribing it when it become invisible. However, in some conditions I do not want to de-subscribe the event even if the control is not visible as I want the result of an operation which is happening on a background thread.
Is there a way through which I can determine if a class has already subscribed to that event?
I know we can do it in the class which will raise that event by checking the event for null
, but how do I do it in a class which will subscribe to that event?
C# Solutions
Solution 1 - C#
The event
keyword was explicitly invented to prevent you from doing what you want to do. It restricts access to the underlying delegate
object so nobody can directly mess with the events handler subscriptions that it stores. Events are accessors for a delegate, just like a property is an accessor for a field. A property only permits get and set, an event only permits add and remove.
This keeps your code safe, other code can only remove an event handler if it knows the event handler method and the target object. The C# language puts an extra layer of security in place by not allowing you to name the target object.
And WinForms puts an extra layer of security in place so it becomes difficult even if you use Reflection. It stores delegate
instances in an EventHandlerList
with a secret "cookie" as the key, you'd have to know the cookie to dig the object out of the list.
Well, don't go there. It is trivial to solve your problem with a bit of code on your end:
private bool mSubscribed;
private void Subscribe(bool enabled)
{
if (!enabled) textBox1.VisibleChanged -= textBox1_VisibleChanged;
else if (!mSubscribed) textBox1.VisibleChanged += textBox1_VisibleChanged;
mSubscribed = enabled;
}
Solution 2 - C#
Assuming that you have no access to the innards of the class declaring the event, you have no way to do it directly. Events only expose operators +=
and -=
, nothing else. You will need a flag or some other mechanism in your subscribing class to know whether you are already subscribed or not.
Solution 3 - C#
/// <summary>
/// Determine if a control has the event visible subscribed to
/// </summary>
/// <param name="controlObject">The control to look for the VisibleChanged event</param>
/// <returns>True if the control is subscribed to a VisibleChanged event, False otherwise</returns>
private bool IsSubscribed(Control controlObject)
{
FieldInfo event_visible_field_info = typeof(Control).GetField("EventVisible",
BindingFlags.Static | BindingFlags.NonPublic);
object object_value = event_visible_field_info.GetValue(controlObject);
PropertyInfo events_property_info = controlObject.GetType().GetProperty("Events",
BindingFlags.NonPublic | BindingFlags.Instance);
EventHandlerList event_list = (EventHandlerList)events_property_info.GetValue(controlObject, null);
return (event_list[object_value] != null);
}
Solution 4 - C#
Simply check whether the control is visible or not whenever the event handler is triggered.
Solution 5 - C#
Can you put the decision making logic into the method that fires the event? Assuming you're using Winforms it'd look something like this:
if (MyEvent != null && isCriteriaFulfilled)
{
MyEvent();
}
Where isCriteriaFulfilled
is determined by your visible/invisible logic.
// UPDATES /////
Further to your 1st comment would it not make sense to alter the behaviour inside your event handler depending on the value of this.Visible
?
a.Delegate += new Delegate(method1);
...
private void method1()
{
if (this.Visible)
// Do Stuff
}
Or if you really have to go with subscribing and unsubscribing:
private Delegate _method1 = null;
...
if(this.visible)
{
if (_method1 == null)
_method1 = new Delegate(method1);
a.Delegate += _method1;
}
else if (_method1 != null)
{
a.Delegate -= _method1;
}
Solution 6 - C#
using System;
//...
public event EventHandler Event;
public bool IsSubscribed(EventHandler Delegate)
{
if (Event == null)
{
return false;
}
var InvocationList = Event.GetInvocationList();
foreach (var Entry in InvocationList)
{
if (Entry.Equals(Delegate))
{
return true;
}
}
return false;
}
After 12 years it is here, works for me.
Solution 7 - C#
Can't you just remember whether you already subscribed? That approach worked fine for me so far. Even if you have a lot of events or objects, you may still want to just remember that (in a dictionary, for example).
On the other hand, visibility change was, at least for me, not a good point to subscribe/unsubscribe. I typically rather go with construction / Disposed, which are more clear than each time visibility changes.
Solution 8 - C#
I'm just expanding on Hans' answer. I'm just trying to ensure that I'm not installing my handler more than once, and not removing it when I still need it. This doesn't protect from a malicious or malfeasant caller unsubscribing repeatedly, for that you'd need to track the callers, and that would just open you up to having repeated subscriptions overrun the tracking mechanism.
// Tracks how many times the ReflectionOnlyResolveHandler has been requested.
private static int _subscribers = 0;
/// <summary>
/// Register or unregister the ReflectionOnlyResolveHandler.
/// </summary>
/// <param name="enable"></param>
public static void SubscribeReflectionOnlyResolve(bool enable)
{
lock(_lock)
{
if (_subscribers > 0 && !enable) _subscribers -= 1;
else if (enable) _subscribers += 1;
if (enable && _subscribers == 1)
AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += ReflectionHelper.ReflectionOnlyResolveHandler;
else if (_subscribers == 0)
AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve -= ReflectionHelper.ReflectionOnlyResolveHandler;
}
}