正确实现 EventArgs Empty

3

我试图正确理解 EventsEventArgs,但我无法完全掌握整个EventArgs.Empty属性。

EventArgs 实现:

public static readonly EventArgs Empty;

它允许我们创建一个事件处理程序并使用以下方式调用:

public event EventHandler<EventArgs> TestHappening;

private void MyMethod()
{
    TestHappening( this, EventArgs.Empty );
}

现在我已经学习了许多基于EventArgs的类,但它们似乎都没有实现这个功能,所以尽管我已经阅读了关于EventArgs.Empty的所有文档,但我还是有点不知所措。 根据文档,“Empty的值是一个只读的EventArgs实例,等同于调用EventArgs构造函数的结果”。

基于此,我创建了以下实现:

public class TestEventArgs : EventArgs
{
    public static readonly TestEventArgs Empty;

    public bool UpdatedValue { get; private set; }

    TestEventArgs()
        : this( false )
    {
    }

    public TestEventArgs( bool updatedValue )
    {
        this.UpdatedValue = updatedValue;
    }
}

public event EventHandler<TestEventArgs> TestHappening;

private void MyMethod()
{
    TestHappening( this, EventArgs.Empty );
}

TestEventArgs.Empty的使用是在实例化一个类还是做了什么?

此外,即使我检查的所有子类都没有使用Empty,它们仍然可以使用,这不会让人感到困惑吗?

最后,根据我研究的各种文档,有两个主要变化,关于何时实际实例化EventArgs。哪一个被认为是“更”正确的呢?

OnTestHappening( new TestEventArgs( false ) );

private void OnTestHappening( TestEventArgs e )
{
    var handler = TestHappening;

    if ( handler != null )
        handler( this, e );
}

vs

OnTestHappening( true );

private void OnTestHappening( bool foo )
{
    var handler = TestHappening;

    if ( handler != null )
        handler( this, new TestEventArgs( foo ) );
}

1
EventArgs类被设计为一个基类。对于那些实际上具有有用属性的更专业的事件类,您不知道什么时候可能需要这样的类。因此,您可以从EventArgs开始,并且可以非常容易地进行更改,而不会破坏任何事件处理程序。在那一天到来之前,您需要传递EventArgs的实例。您可以使用new EventArgs(),但这是浪费的,EventArgs.Empty是一种廉价的替代方法。 - Hans Passant
1
首先,这个类的“空”实例本身就没有意义。在创建这个类时,应该期望人们在创建实例时提供一个布尔值,而不是使用默认值。 - Servy
1
@Servy:我同意,我只是想完全理解它的工作原理,并且认为如果我像我所做的那样将其分解,可能会有一天帮助其他迷失的灵魂 :) - Storm
1
@Storm 理解如何实现在特定上下文中没有意义的东西,并不能教你如何正确使用该技术。如果你想学习如何使用一种技术,你应该在适合使用它的上下文中使用它作为工具。 - Servy
1个回答

2
如果您不需要使用“Empty”字段,那么您应该自己考虑一下。如果不需要使用它,就不要创建它。EventArgs类没有任何变量或属性,因此每次都创建一个新实例是没有意义的。在您的情况下,由于您有默认值,因此创建两个“空”的TestEventArgs更有意义,一个用于true,另一个用于false(如果在您的场景中真的有意义)。
您的实现还存在其他问题,我已经进行了修复。
public class TestEventArgs : EventArgs
{
    public static readonly TestEventArgs True = new TestEventArgs(true);

    public static readonly TestEventArgs False = new TestEventArgs(false);

    public bool UpdatedValue { get; private set; }

    public TestEventArgs(bool updatedValue)
    {
        this.UpdatedValue = updatedValue;
    }

    public event EventHandler<TestEventArgs> TestHappening;

    private void MyMethod()
    {
        EventHandler<TestEventArgs> eh = TestHappening;

        eh?.Invoke(this, TestEventArgs.True);
    }
}

我所做的更改:
  1. Empty实例化为一个new TestEventArgs,因为这也是EventArgs.Empty的定义。
  2. 我已经实现了线程安全版本的事件处理程序(您代码中的第二个示例)。如果订阅事件的事件处理程序列表发生变化,则您的第一个示例不安全。您的第二个示例是安全的,因此应该选择该选项。
关于您最后的观点:这取决于您是否打算让调用委托更改对象(它是传递一个实例还是多个实例)。在您的情况下,由于实例无法更改,所以没有那么重要。

好的,现在这就有了很多意义,我之前完全看不懂,可能是想太多了。关于线程安全性,感谢指出,我仍在努力理解它,为什么 TestHappening(this, EventArgs.Empty) 不是线程安全的?你有可以进一步解释的链接吗?我已经阅读了很多关于线程安全性的文章,但似乎总是不能掌握。 - Storm
帅气的Patrick,你能否在回答中也回答一下我上面的第三个问题吗? - Storm
为什么在readonly之前要使用关键字new?我认为这是多余的,因为True字段没有隐藏任何内容,或者我错了吗?eh(this, TestEventArgs.Empty);行无法编译,因为Empty返回基类型EventArgs,无法向下转换为TestEventArgs。如果您隐藏Empty字段而不是创建TrueFalse,则您的示例将更有意义。除此之外,您可能还想从UpdateValue中删除private set,因为它是只读的,也许将线程安全调用更新为TestHappening?.Invoke(this, TestEventArgs.True); - fibriZo raZiel
1
@fibriZoraZiel 你是对的。已修复。感谢您的评论。 - Patrick Hofman

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