5

基本上这已经在我脑海中有一段时间了......我想看看你的意见

我读了 Jon Skeet 的一本名为:C# in Depth, Second Edition 的好书,并且建议在声明自定义事件时使用类似这样的东西:

public event Action<string> MyEvent = delegate { };

此声明将在触发事件之前将我们从无效性检查语句中释放出来,所以不要这样:

    if (this.MyEvent != null)
    {
        this.MyEvent("OMG osh");
    }

我们可以简单地调用:

this.MyEvent("OMG osh");

我们的代码就可以工作了。

当您像这样声明事件时,该事件将使用空委托初始化,因此我们不需要检查是否为空。

这是声明事件的另一种方式,它们是等价的

private Action<string> myDelegate;
public event Action<string> MyEvent
{
    add
    {
        this.myDelegate += value;
    }
    remove
    {
        this.myDelegate -= value;
    }
}

欲了解更多信息:http ://csharpindepth.com/Articles/Chapter2/Events.aspx

我刚刚和一些同事讨论过,在工作中,我们正在讨论这个话题,他们争辩说有时我们需要一次清除事件的所有订阅,我们可以简单地将 null 分配给后面的代表如果我们想继续使用相同的模式,我们将不得不使用空委托重新初始化事件。他们还问在多线程场景中会发生什么,这就是我提出这个问题的主要原因

问题

  1. 我想知道在声明遵循此模式的事件与检查 null 时是否存在一些隐藏的含义(可能在使用多线程时)

  2. 如果我使用这种模式,我将有效地删除几个if条件,从而删除跳转语句。这在整体性能方面不是更好吗?

干杯

4

2 回答 2

3

我对性能的兴趣不如开发团队的可读性和期望。您真的不希望其他不了解使用模式的开发人员认为,“啊!我看到有人忘记正确使用委托调用并检查 null”

我一直明白,要处理多线程场景,您仍然需要制作底层 Delegate 的本地副本,以便即使在调用订阅者时在该列表的迭代期间订阅者列表发生更改,您也可以向所有订阅者触发。

话虽如此,我仍然想知道使用此扩展方法来保存样板代码(具有多个参数的重载)

如果订阅者不需要知道谁触发了事件,我倾向于使用 Action 而不是 EventHandler 事件。

public static class ActionExtension
{
    public static void SafeInvoke<T>(this Action<T> action, T arg)
    {
        var temp = action;
        if (temp != null)
        {
            temp(arg);
        }
    }
}

public event Action<string> InterestingEvent;
// event invoker
InterestingEvent.SaveInvoke("Boo!");
于 2012-07-18T08:04:37.427 回答
1

首先:你确定你没有过早优化吗?您是否真的面临性能问题,因为不是检查 null,而是在触发事件时调用了一个额外的空方法?如果没有,那么我建议你放弃这个问题。

现在,对于您的问题:我认为可以肯定地说检查 null多余的方法调用便宜,所以如果您想要尽可能好的性能,那么 Jon Skeet 的便捷delegate { }模式可能不适合您。

当涉及到多线程时,一个更重要的问题是定义将在哪个线程/在哪个上下文(例如SynchronizationContext)中调用每个事件处理程序。您应该能够将此决定留给您的事件的每个消费者,因为一些事件处理程序不会关心,其他将希望转发到正确的上下文(例如通过SynchronizationContext.Post)。

如果您确实决定检查 null,请注意,而不是这个:

if (this.MyEvent != null)
{
    this.MyEvent("OMG osh");
}

建议在多线程场景中,改为这样做:

Action<string> handlers = this.MyEvent;
if (handlers != null)
{
    handlers("OMG osh");
}

也就是说,首先将委托复制到局部变量中。这是因为委托变量可能在检查空值期间被另一个线程操作。(我不太确定这是否真的有必要,但这是建议的模式。)


题外话:我认为你有一个小错误:

[W] 当您声明一个事件时,实际发生的是您声明 adelegate和 an event

这是正确的,但值得注意的是,对于 CLI,事件只是几个方法的组合(例如add,removeraise可能的其他访问器)。几乎所有其他内容实际上都是特定于语言的。例如,C# 中的事件只不过是一个委托,与一些访问器方法配对,以及对委托可以做什么的一些额外限制(即+=,并且-=是来自事件所在类型之外的委托的唯一有效操作)被声明)。

于 2012-07-18T08:02:03.400 回答