74

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?

Mark Good
  • 4,271
  • 2
  • 31
  • 43
Ram
  • 11,404
  • 15
  • 62
  • 93
  • 2
    Check this link http://social.msdn.microsoft.com/forums/en-US/csharpgeneral/thread/d7d8791f-6aef-4fda-ae0e-5eddcb856706/ – Oskar Kjellin Apr 23 '10 at 09:00
  • if its only about wether *anyone* is subscribed:´bool subscribedTo = theEvent != null´ – Mark Jul 23 '14 at 14:24

8 Answers8

94

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;
}
Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • 5
    Would you happen to have any blogs, channel 9 videos, or msdn articles that talk about the design methodology behind making *events* so difficult to interact with? Perhaps if i understood the *why*, and what the *intended* mechanisms to accomplish some (normally) trivial things, i might have an easier time coming up with trivial solutions to my own problem sets. – Ian Boyd Nov 17 '11 at 23:38
  • 3
    Your events are not difficult to interact with. Messing with *somebody else's* events is made difficult, that's messing with private parts. Ask a question about it. – Hans Passant Nov 18 '11 at 00:18
  • 3
    Okay, *why* is messing with someone else's events difficult? – Ian Boyd Nov 18 '11 at 02:13
  • 3
    What if here are multiple subscribers?? I don't want to register the same subscriber twice... I don't want the event to be fired twice for any single subscriber. The problem is also relevant when the subscribers are remote entities, over a wcf channel. – Charles Bretana Oct 25 '13 at 01:27
  • The WinForms comment is really incongruous...the OP never mentioned winforms so why even bring it up? – ΩmegaMan Jun 16 '16 at 15:27
  • 13
    Oh, give me some credit to know that when this user is talking about controls then he means [winforms]. Not hard to find out from his profile. – Hans Passant Jun 16 '16 at 15:42
8

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.

Gorpik
  • 10,940
  • 4
  • 36
  • 56
7
  /// <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);
  }
SwDevMan81
  • 48,814
  • 22
  • 151
  • 184
2

Simply check whether the control is visible or not whenever the event handler is triggered.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Amry
  • 4,791
  • 2
  • 23
  • 24
  • I don't want to do that as the events are triggered at regular interval and I want to use them only when my control is invisible. if I do what you are saying, it will be a performance hit. – Ram Apr 23 '10 at 10:09
  • @Ram: Why do you think it will be a performance hit? Have you measured the change in performance? – Phil Gan Apr 23 '10 at 10:38
  • @Phil: Hi Phil, It is a performance hit as I am doing this with multiple forms and multiple events. Each form process the data diff way. So to avoid processing of data I am subscribing the events only is form is visible. I believe using boolean would be a good option. – Ram Apr 23 '10 at 10:51
1

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;
} 
Phil Gan
  • 2,813
  • 2
  • 29
  • 38
  • 1
    I wonder if `areCriteriaFulfilled` is better grammatically speaking? – Phil Gan Apr 23 '10 at 09:13
  • I am doing as follows if(this.visible) { a.Delegate += new Delegate(method1); } else { a.Delegate -= new Delegate(method1); } – Ram Apr 23 '10 at 09:51
  • I don't want to do as suggested by you as the events are triggered at regular interval and I want to use them only when my control is invisible. if I do what you are saying, it will be a performance hit. – Ram Apr 23 '10 at 10:35
  • @Ram: Updated again. But I'm still curious as to why you think it will impact performance? Subscribing to an event also has an overhead of allocating and (eventually) deallocating memory. – Phil Gan Apr 23 '10 at 10:46
  • @Phil : Yes, I do agree that it will increase overhead. thanks. :) – Ram Apr 23 '10 at 11:56
1
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.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
Lex
  • 29
  • 1
  • 4
0

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.

OregonGhost
  • 23,359
  • 7
  • 71
  • 108
0

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;
    }
}
Darrel Lee
  • 2,372
  • 22
  • 22
  • The callers above aren't really "subscribers", they are just asking the helper class to register its handler. – Darrel Lee Jul 16 '16 at 09:23