C++/CLI事件有任何侦听器吗?

5

我可以在C#中检查事件是否有任何监听器:

C#示例:

public static event EventHandler OnClick;

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

在C++/CLI中,检查事件是否为空是不必要的。
C++/CLI示例:
delegate void ClickDelegate( Object^ sender, MyEventArgs^ e );
event ClickDelegate^ OnClick;

OnClick (sender, args);

但是,在我正在处理的项目中,如果没有监听器,我不想构造MyEventArgs对象。

在C++中,如何判断OnClick是否有任何监听器?


1
构建MyEventArgs真的很耗费时间吗?这对您来说很重要吗? - svick
如果Richard的问题和我的一样,那么问题不在于创建一个对象很昂贵,而是这个事件每秒钟发生多次,跳过它可以节省大量处理器周期。 - Austin Mullins
2个回答

3

根据与@BenVoigt在@svick的原始答案和新的MSDN关于C++/CLI事件的文章的评论讨论,我创建了一个最小的示例来正确地执行此操作。这段代码在针对.NET 4.5的Visual Studio 2013 CLR项目模板中编译和运行。我没有在其他运行时和目标上进行测试,但它只使用基本的.NET组件。

简而言之:

  • 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

我的解决方案:

// 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();
}

是我太笨还是这个判断 if(source == nullptr) 真的很复杂? - Jay
是的,不幸的是内置的C++/CLI工具无法让你访问源代码。你必须用能让你看到它的东西来包装它,而且由于你自己在做,你必须确保另一个线程没有在背后设置源代码。 - Austin Mullins

2

看起来你不能使用"trivial events"这样的方式进行检查,因为你没有直接访问底层字段的权限(就像C#中的自动属性一样)。

如果你想要做到这一点,可以明确指定事件的访问器方法和后备字段。请参见如何定义事件访问器方法以了解具体操作方法。


@BenVoigt和Svick,如果MSDN样例中存在反模式,你们可以在这里发布更好的示例吗? - Austin Mullins
@AustinMullins:回顾一下代码,最显眼的问题是大量使用了 Delegate::CombineDelegate::Remove 调用,这些调用是不安全的类型。然后有很多的转换操作。+-+=-= 运算符可以正确地在委托变量上运行并且是类型安全的。我相信我能找到其他的问题。 - Ben Voigt
1
@AustinMullins:页面上建议将其移动到那里,不是吗?实际上它在这里:http://msdn.microsoft.com/en-us/library/dw1dtw0d%28v=vs.100%29.aspx 看起来最新版本的文档中完全删除了该示例,因为有人意识到它有多糟糕 - 不过您仍然可以阅读旧版文档。 - Ben Voigt
1
是的,还有其他问题。在空值检查和调用之间存在竞争条件。 - Ben Voigt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接