views:

129

answers:

4

Is it possible to stop multiple subscribers from subscribing to an event?

I have created a quick example snippet to give my question some context but unfortunately I can't test it right now because I'm not at my VS machine.

The goal is to:

  • Return an empty list if there are no subscribers.
  • Return the return value of a single subscriber.
  • Throw an exception if more than one subscriber tries to subscribe to the event (this is the crux of the matter).

Is this possible?

public delegate List<IBaseWindow> GetWindowListDelegate();
public static event GetWindowListDelegate GetWindowListEvent;

public List<IBaseWindow> GetWindowList() {

    if (GetWindowListEvent == null) {
        return new List<IBaseWindow>();
    }

    return GetWindowListEvent();
 }

Note: I'm using .NET 3.5 sp1.

+5  A: 

You could use event accessors to accomplish this. Something like the following:

  private EventHandler _h;
  public event EventHandler H {
      add {
         if (...) { // Your conditions here.
                    // Warning (as per comments): clients may not
                    // expect problems to occur when adding listeners!
           _h += value;
         }
      }
      remove {
         _h -= value;
      }
  }

As Andrew points out, you don't really need events to accomplish this. Is there some particular reason you need them?

John Feminella
+1 as an answer. I would add to be careful with this technique - when a .NET developer sees a type declare an event, the *safe* assumption (so safe there's not even a reason to check otherwise) is that new listeners can be added without problems.
280Z28
+1 both for the answer and for the comment by @280Z28.
Fredrik Mörk
Good call. I've added a note.
John Feminella
There isn't a reason other than my 'metal framework not seeing an alternative'. I've asked Andrew to provide an example while I go and read up on delegates and how to use them without events. Sorry if this gap in my knowledge has created a crazy question! :|
CuriousCoder
@curiouscoder: that is what SO is all about; close those gaps.
Fredrik Mörk
+7  A: 

It sounds like you don't need an event - just expose the delegate itself and allow callers to set the delegate reference on their own.

Andrew Hare
Sorry but I don't understand delegate's well enough to know what you mean... my understanding of delegates is limited to their use with events. Perhaps that has limited how I'm approaching this problem. Could you provide an example while I go and read up on delegates?
CuriousCoder
There's nothing difficult to understand, just remove the `event` keyword, and "subscribe" with = instead of +=
Thomas Levesque
AFAIK all delegates in .NET are by default multi-cast delegates, i.e. they can have multiple "subscribers".
dtb
Thanks for the clarification Thomas... makes sense to me now. Impressive knowledge transfer in 1 sentence! :D
CuriousCoder
A: 

It is certainly possible to achieve what you want to do, but it does not conform to convention -- I would urge you to come up with a different solution that does not involve events.

As explained by Jon Skeet, public events are property-like wrappers around a multicast delegate.

You can think of the standard implementation of an event as a list of functions to be called when something happens.

// From the above link:

// the exposed event
public event EventHandler MyEvent

// multicast delegate field
private EventHandler _myEvent;

// property-like add & remove handlers
public event EventHandler MyEvent 
{
    add
    {
        lock (this)
        {
            _myEvent += value;
        }
    }
    remove
    {
        lock (this)
        {
            _myEvent -= value;
        }
    }        
}

... when a developer sees such an event, they expect to be able to subscribe to it without issue as an event basically says "any number of types may register to receive this event notification".

It seems that you are wanting to allow someone to set the implementation that gets the list of windows. What I'd suggest doing is allowing people to manually pass in a delegate, then hold a single delegate instance. Make it explicit that there is only one way to set it; if at all possible I would recommend using constructor injection as this removes all ambiguity -- you can only set the delegate instance once on construction, then it is no longer modifiable by the public API (so class B cannot clobber the delegate that was already set by class A).

E.g.

public class CallsDelegateToDoSomething
{      
   private Func<List<IBaseWindow>> m_windowLister; 

   public CallsDelegateToDoSomething(Func<List<IBaseWindow>> windowFunc)
   {
       m_windowLister = windowFunc;
   } 

   public List<IBaseWindow> GetWindowList() 
   {    
       if (windowLister == null) 
       {
           return new List<IBaseWindow>();
       }

       return m_windowLister();
   }
}

If your design doesn't allow for this, then just create SetWindowLister(Func<List<IBaseWindow>> windowLister) and ClearWindowLister() methods instead.

Mark Simpson
Even a "single delegate" can result in the invocation of multiple targets.
dtb
Thank you for such a complete answer and the final example. Im taking a further look at delegates now to understand dtb's comment.
CuriousCoder
Mark Simpson
IMO delegates are the wrong tool to get what the OP tries to achieve -- the question sounds more like a case of specialisation. The best solution would probably be an object implementing some interface to be passed in, or making the class abstract and requiring specialised derived classes that fill in the missing bits. But since the code is just an example, it's hard to tell. I second the statement that restricting events and single delegates to a single target is not a good idea.
dtb
Yup, I agree with everything you just said.
Mark Simpson
+2  A: 

Just to complete John's answer, here's a working implementation of an event that only allows one handler :

class Foo
{
    private EventHandler _bar;
    public event EventHandler Bar
    {
        add
        {
            if (_bar != null || value.GetInvocationList().Length > 1)
            {
                throw new InvalidOperationException("Only one handler allowed");
            }
            _bar = (EventHandler)Delegate.Combine(_bar, value);
        }
        remove
        {
            _bar = (EventHandler)Delegate.Remove(_bar, value);
        }
    }
}

Note that exposing a delegate rather than an event doesn't prevent multiple handlers : since .NET delegates are multicast, one delegate can represent a call to multiple methods. You could however expose the delegate as a property, and perform in the setter the same check as in the code above.

Anyway, as others have pointed out, it's probably not a good idea to prevent multiple handlers for an event... it would be very confusing to developpers who use it.

Thomas Levesque
Thank you for taking the extra time to provide some further information. I am definitely of the mind set now that the initial design was wrong.
CuriousCoder