5

In C# I can check if an event has any listeners:

C# Example:

public static event EventHandler OnClick;

if (OnClick != null)
    OnClick(null, new EventArgs() );

In C++/CLI checking if the event is null is not necessary.

C++/CLI Example:

delegate void ClickDelegate( Object^ sender, MyEventArgs^ e );
event ClickDelegate^ OnClick;

OnClick (sender, args);

BUT, in the project I am working on, I don’t want to construct the MyEventArgs object if there are no listeners.

How do I find out if OnClick has any listeners in C++?

svick
  • 236,525
  • 50
  • 385
  • 514
Richard
  • 379
  • 4
  • 17
  • 1
    Is constructing the `MyEventArgs` really so expensive that it matters to you? – svick Dec 07 '11 at 10:17
  • @svick If Richard's problem is like mine, the issue isn't that creating one object is expensive, but that this event happens several times a second, and it would save a lot of processor cycles to just skip it. – Austin Mullins Aug 01 '14 at 20:44

2 Answers2

3

Based on the comment discussion with @BenVoigt on @svick's original answer and the new MSDN article on C++/CLI events, I have created a minimal example of how to do this correctly. This code compiles and runs in a Visual Studio 2013 CLR project template targeting .NET 4.5. I haven't tested on other runtimes and targets, but it only uses basic .NET components.

TL;DR:

  • Make the backing field private

  • Lock each add, remove, and raise call with System::Threading::Monitor

  • Use the standard event handler convention:

      void MyEventHandler(Object ^sender, MyEventArgs ^e);
    
  • Use += and -= except when the backing field is a nullptr

My Solution:

// compile with: /clr
#include "stdafx.h"

using namespace System;
using System::Threading::Monitor;

public delegate void MyDelegate(Object ^sender, EventArgs ^e);

ref class EventSource {
private:
    MyDelegate ^myEvent;
    Object ^eventLock;

public:
    EventSource()
    {
        eventLock = gcnew Object();
    }

    event MyDelegate^ Event {
        void add(MyDelegate^ handler) {
            Monitor::Enter(eventLock);
            if (myEvent == nullptr)
            {
                myEvent = static_cast<MyDelegate^> (
                            Delegate::Combine(myEvent, handler));
            }
            else
            {
                myEvent += handler;
            }
            Monitor::Exit(eventLock);
        }

        void remove(MyDelegate^ handler) {
            Monitor::Enter(eventLock);
            if (myEvent != nullptr)
            {
                myEvent -= handler;
            }
            Monitor::Exit(eventLock);
        }

        void raise(Object ^sender, EventArgs ^e) {
            Monitor::Enter(eventLock);
            if (myEvent != nullptr)
                myEvent->Invoke(sender, e);
            Monitor::Exit(eventLock);
        }
    }

    void Raise()
    {
        Event(this, EventArgs::Empty);
    }
};

public ref struct EventReceiver {
    void Handler(Object ^sender, EventArgs ^e) {
        Console::WriteLine("In event handler");
    }
};

int main() {
    EventSource ^source = gcnew EventSource;
    EventReceiver ^receiver = gcnew EventReceiver;

    // hook event handler
    source->Event += gcnew MyDelegate(receiver, &EventReceiver::Handler);

    // raise event
    source->Raise();

    // unhook event handler
    source->Event -= gcnew MyDelegate(receiver, &EventReceiver::Handler);

    // raise event, but no handlers
    source->Raise();
}
Community
  • 1
  • 1
Austin Mullins
  • 7,307
  • 2
  • 33
  • 48
  • is it me, or is this really complicated in order to do if(source == nullptr) ? – Jay Sep 13 '17 at 15:41
  • Yeah, unfortunately the built-in C++/CLI stuff doesn't let you access the source. You have to wrap it up with something that does let you see it, and since you're doing it yourself, you have to make sure another thread isn't setting the source behind your back. – Austin Mullins Sep 14 '17 at 16:40
2

It seems you can't check that with "trivial events", like you used, because you don't have direct access to the underlying field (as with auto-implemented properties in C#).

If you want to do this, you can specify the event's accessor methods and the backing field explicitly. See How to: Define Event Accessor Methods on how exactly to do that.

svick
  • 236,525
  • 50
  • 385
  • 514
  • @BenVoigt and Svick, if there are anti-patterns in the MSDN sample, can you post better examples here? – Austin Mullins Aug 01 '14 at 20:43
  • @AustinMullins: Just taking a quick glance back, the thing that stands out is a large number of `Delegate::Combine` and `Delegate::Remove` calls, which are not type-safe. And then a large number of casts. The operators `+`, `-`, `+=`, and `-=` work just fine on delegate variables and are type-safe. I'm sure I could find other problems. – Ben Voigt Aug 01 '14 at 20:46
  • 1
    @AustinMullins: The page suggests it moved there, doesn't it? Actually it's [here](http://msdn.microsoft.com/en-us/library/dw1dtw0d%28v=vs.100%29.aspx) Looks like that example was entirely removed in the latest version of the documentation because someone realized how bad it was -- you can still read the older documentation though. – Ben Voigt Aug 01 '14 at 20:48
  • 1
    Yes there are other problems. There's a race-condition between the null check and the call. – Ben Voigt Aug 01 '14 at 20:50