如何在C#中将事件传递给函数?

20

我希望将一个事件传递给一个辅助函数。这个函数将向事件附加一个方法。然而,我在正确传递事件方面遇到了麻烦。我尝试过传递EventHandler<TEventArgs>,它能编译通过,但是事件没有被附加(但仍然被添加;似乎是事件处理程序的副本被创建了)。

举例来说,如果我有这个:

public event EventHandler<EventArgs> MyEvent;

并且这个帮助函数:

public static void MyHelperFunction<TEventArgs>(EventHandler<TEventArgs> eventToAttachTo)
{
    eventToAttachTo += (sender, e) => { Console.WriteLine("Hello world"); };
}

同时调用方:

MyHelperFunction(MyEvent);
MyEvent(null, new EventArgs()); // Does nothing.

@Strager:您介意详细说明一下您如何使用它吗?我觉得这个问题非常有趣,但是我很难看到使用案例。 - John Feminella
@John Feminella,我正在创建一些辅助函数,并且其中一个是同步等待事件的。它们主要用于减少代码复用,用于多个WaitFor方法(例如WaitForConnected)在我的网络类中(这些类是异步操作)。 - strager
7个回答

17

+= 操作符应用于委托类型时会创建一个新的委托,该委托是旧委托和新委托结合而成的,它不会修改现有的委托,因此这就是为什么它不能正常工作的原因。

要使其正常工作,您需要通过引用传递委托。

public static void Helper(ref EventHandler<EventArgs> e)
{
    e+= (x,y) => {};
}

这段代码在方法外运行正常的原因是 LHS 仍然是实际字段。因此,+= 将创建一个新的委托并分配回成员字段。


有趣。所以我猜+=重新创建了委托(就像e = e + func)。谢谢你的帮助!我花了几个小时尝试调试我的代码(归咎于线程),而事实上事件没有在应该被触发时被触发。 - strager
4
使用ref时出现问题:如果我使用Helper(ref myClassInstance.MyEvent),就会出现以下错误:事件MyEvent只能出现在+=或-=的左侧(除非在类型MyClass内部使用)。我该如何解决这个问题? - strager
我也遇到了同样的问题。你找到解决左侧错误的方法了吗? - Jason Coyne
我也遇到了左侧的错误。您能否提供一下您的静态 Helper 类的使用示例? - Doctor Blue
@AlexBurtsev 在这里有点晚了,但是“这不会起作用”并不在所有情况下都是100%正确的。如果拥有事件的类是传递引用的类,则可以正常工作。您只需不能将引用传递给另一个类的事件。为了解决此问题,您可以在调用类中订阅其他人的事件。在该事件处理程序中,使用相同的签名触发自己的事件。然后,将对自己的事件的引用传递给辅助方法。 - Pete Baughman
显示剩余2条评论

3

我刚刚想到了这个小帮手。如果它是你自己创建的事件,你可以使用这样的包装器。你可以像平常一样使用 += 操作符来附加处理程序,但是可以将包装器传递并甚至从其他地方触发事件。

public class GenericEvent<T> where T:EventArgs
{
    public event EventHandler<T> Source = delegate { };

    public void Raise(object sender, T arg = default(T))
    {
        Source(sender, arg);
    }

    public void Raise(T arg = default(T))
    {
        Source(this, arg);
    }

    public void AddHandler(EventHandler<T> handler)
    {
        Source += handler;
    }

    public void RemoveHandler(EventHandler<T> handler)
    {
        Source -= handler;
    }

    public static GenericEvent<T> operator +(GenericEvent<T> genericEvent, EventHandler<T> handler)
    {
        genericEvent.AddHandler(handler);
        return genericEvent;
    }
}

创建事件的方式如下:

public GenericEvent<EventArgs> MyEvent = new GenericEvent<EventArgs>();

附加处理程序:

MyEvent += (s,e) => {};

触发事件:

MyEvent.Raise();

1
一个好的简单类。为了让它变得更复杂,我建议使用一个订阅事件的事件列表。如果GenericEvent对象被处理,它将取消订阅所有已订阅的事件。这可能意味着GenericEvent实现了IDisposable接口。 - J Pollack

2

虽然不太好,但您可以使用反射来完成此操作。

    public EventMonitor(object eventObject, string eventName)
    {
        _eventObject = eventObject;
        _waitEvent = eventObject.GetType().GetEvent(eventName);

        _handler = new EventHandler(SetEvent);
        _waitEvent.AddEventHandler(eventObject, _handler);
    }

当 eventObject 是包含事件的对象,eventName 是事件的名称时,SetEvent 是您的事件处理程序。

我还有一个像这样的 dispose 方法:

    public void Dispose()
    {
        _waitEvent.RemoveEventHandler(_eventObject, _handler);
    }

@Kairan - 我不知道。那是三年半以前的事了。 - Ray

2
只是猜测:你是否尝试将其作为引用传递?
public static void MyHelperFunction<TEventArgs>(ref EventHandler<TEventArgs> eventToAttachTo)

MyHelperFunction(ref MyEvent);

1

正如许多人指出的那样,将事件传递给方法要么不可能,要么不简单。

  1. 请澄清一下,但我怀疑您的预期用法将类似于:

    void Register()
    {
        var super = new SuperHandler();
    
        //not valid syntax:
        super.HandleEvent(MyEvent1);
        super.HandleEvent(MyEvent2);
        super.HandleEvent(MyEvent3);
        super.HandleEvent(MyEvent4);
    }
    

    您可以通过使您预期的通用事件处理程序公开访问(如果需要,也可以是内部访问)来轻松实现此目的:

     public static class GenericHandler
     {
         public static void HandleAnyEvent(object sender, EventArgs e)
         {
             //handle
         }
     }
    
     public class SomeClass
     {
         void RegisterEvents()
         {
             var r = new EventRaiser();
    
             r.ImportantThingHappened += GenericHandler.HandleAnyEvent;
         }
     }
    

    在此示例中,我的全捕获处理程序位于静态类中,但您也可以使用非静态类。 此外,我看到您在示例中使方法成为泛型(TEventArgs)。 由于所有EventHandler派生类(例如CancelEventHandler)都与基本EventHandler匹配,因此您不需要涉及泛型(也不会有帮助)。

  2. 如果注册逻辑复杂或必须保持EventHandler私有,请考虑使用接口事件。 这可能无法满足您减少代码量的预期目标,但它将允许您创建一个可以可预测地处理特定类型的所有事件的类。

    interface IRaiseEvents
    {
        event EventHandler ConnectionCreated;
        event EventHandler ConnectionLost;
    }
    
    public class SuperHandler
    {
        void RegisterEvents(IRaiseEvents raiser)
        {
            raiser.ConnectionCreated += (sender, args) => Console.WriteLine("Connected.");
            raiser.ConnectionLost += (sender, args) => Console.WriteLine("Disconnected.");
        }
    }
    

1
我有一个解决方案,其中有两个接口。第一个接口具有绑定某些事件的方法,而另一个接口具有可以绑定到这些事件的事件方法。
第一个接口的绑定方法将第二个接口作为参数,这使得可以将事件绑定到实现第二个接口的任何类的事件方法上。
这是否容易理解,或者您需要一些代码? :)

1
将类似 "Action e = e => myevent += e;" 的内容传递,并从包含处理程序的方法中调用?这样做的好处是可以与.NET类一起使用。

你说得对。如果我现在要解决这个问题,那可能就是我会做的事情了。我四年前提出了这个问题(!),那时候我对C#的了解很少。尽管如此,感谢您添加答案;我相信它将对未来的某个人有所帮助! - strager

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