【问题标题】:Is there a way to accomplish the equivalent of passing an "event" by reference?有没有办法完成通过引用传递“事件”的等价物?
【发布时间】:2011-08-16 22:58:25
【问题描述】:

我将“事件”放在引号中,因为我意识到它是一种语法糖,而不是真正的类型。

我有一些事件被简单地链接到另一个类中的匹配事件。所以当事件引发时,段落就像

Raiser -> 代理 -> 订阅者

所以在代理类中我有一个像这样的通用模式:

Raiser.SomeEvent += 
    (_, args) =>
    {
        if (this.SomeEvent != null)
            this.SomeEvent(this, args);
    };

为了整理我的代码,我想将它移到另一个方法,该方法返回一个包装了上述事件调用代码的新委托:

public static EventHandler GetHandlerDelegate(EventHandler handler, Object sender)
{
    return
        (_, args) =>
        {                
            if (handler != null)
                handler(sender, args);
        };
    }      

然后在代理中我可以这样做:

Raiser.SomeEvent += GetHandlerDelegate(this.SomeEvent, this);

哪个更整洁。

好吧,只要订阅者在上述调用之后 不决定订阅 Proxy.SomeEvent 就可以了。不幸的是,我没有像我希望的那样通过引用传递“事件”;我现在明白我只是传递调用列表,所以当OtherClass.SomeEvent 发生并且调用匿名方法并调用它所提供的“事件”(委托)时,只有已添加到该事件的委托在我调用 GetHandlerDelegate() 的时候会被调用。虽然这实际上足以满足我目前的情况,但以这种方式编码确实是不可接受的。

我已经阅读了其他一些 SO 问题,并且我收集了一些称为 Reactive Extensions 的东西可能会有所帮助,但目前我正在寻找一种更简单的解决方案(如果有的话)。 (如果没有,我就不会这样做。)

有没有其他方法可以完成我正在尝试做的事情,而没有上述缺点?


如果这个问题不清楚,请参阅my answer,希望有助于澄清它。

【问题讨论】:

    标签: c# .net events delegates anonymous-methods


    【解决方案1】:

    编辑:好的,我想我现在明白了。其实很简单。您应该能够编写代理以仅处理一个事件,然后让代理本身订阅 Raiser 的事件,就像这样(仅针对 EventHandler - 我稍后会谈到):

    Proxy proxy = new Proxy();
    raiser.SomeEvent += Proxy.Handler;
    
    // Then in the subscriber...
    proxy.ProxiedEvent += (whatever)
    
    // And the proxy class...
    public class Proxy
    {
        public event EventHandler ProxiedEvent;
    
        public void Handler(object sender, EventArgs e)
        {
            EventHandler proxied = ProxiedEvent;
            if (proxied != null)
            {
                // Or pass on the original sender if you want to
                proxied(this, e);
            }
        }
    }
    

    现在,这里的困难是让它通用。我目前想不出任何办法,虽然我现在有点分心。

    这是您在考虑的那种事情,还是至少可以帮助您以不同的方式思考事情?

    【讨论】:

    • 乔恩,谢谢你的回答,但作为一个普通的亚飞碟,我很难看到隐含的细节。换句话说,我迷路了。你能详细说明一下吗?如何将其应用于我的问题?
    • @Charles:我可能误解了这个问题......我现在觉得有点难以理解,但这很可能是我和你自己的错。但是,这里的 EventWrapper 基本上是一种抽象“可以订阅和取消订阅的东西”概念的方式。从根本上说,这就是一个事件。您将无法将事件本身作为委托来获取,因为它不是委托。
    • 我似乎有过表达我的问题的糟糕历史,所以我确信我应该为任何混乱负责,而不是你!本质上,我想将任何委托 dA 传递给一个方法并取回一个调用 dA 的新委托 dB,如果 dA然后 i>dA 的调用列表发生变化,我希望在调用 dB 时调用 updated 调用列表。 (我在没有使用任何事件的情况下进行了尝试 - 只是一个代表 - 为自己确认问题实际上与 delegates 的工作方式有关)。
    • @Charles:这本身是不可能的,因为委托的调用列表永远不会改变——委托是不可变的。但是,我想我现在明白你的意思了,并将编辑我的答案......
    • 再次感谢您的努力。不过,似乎不变性不一定是障碍:假设委托就像一个字符串,我可以用 ref 关键字传递它——那么我的问题就解决了。关于您的编辑:我的目标是让代理使用最少的代码订阅 raiser - 例如一个简单的 lambda。我了解您向我展示的内容,但那是长版本。不过,我找到了妥协;我会发布一个答案。
    【解决方案2】:

    因为我最初的目标是:

    Raiser.SomeEvent += GetHandlerDelegate(this.SomeEvent, this); 
    

    是不可能的,我已经妥协并想出了这个:

    Raiser.SomeEvent += (_, args) => RaiseEvent(this.SomeEvent, this, args); 
    

    GetHandlerDelegate() 会返回一个引发事件的委托,而RaiseEvent() 只是(你猜对了)引发事件。

    public static void RaiseEvent(EventHandler _event, Object sender, EventArgs args)
    {
        if (_event != null)
            _event(sender, args);
    }
    

    并使用自定义 EventArgs 支持事件:

    public static void RaiseEvent<TArgs>(EventHandler<TArgs> _event, Object sender, TArgs args)
        where TArgs : EventArgs
    {
        if (_event != null)
            _event(sender, args);
    }
    

    我已经把这些方法放在了一个静态帮助类中,所以实际的调用有点难看;这是一个例子:

    ViewControl.OpenFilesetClick += (_, args) => EventHelper.Raise(OpenFilesetClick, this, args); 
    

    (我还将方法重命名为 Raise() 并从传递的事件名称中删除了可选的 this)。

    但我不完全相信这是否值得,考虑到替代方案可以说更容易阅读:

    ViewControl.OpenFilesetClick += (_, args) => 
    {
        if (OpenFilesetClick != null)
            OpenFilesetClick(this, args);
    };
    

    无论如何,这是一种有趣的方式来详细了解事件和委托如何工作(或它们如何不工作)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-03-14
      • 2020-03-23
      • 2019-08-19
      • 1970-01-01
      • 2013-11-17
      • 2010-11-04
      相关资源
      最近更新 更多