【问题标题】:Singleton events单例事件
【发布时间】:2015-06-23 16:32:52
【问题描述】:

我正在重构一些旧代码,其中有很多像这样的静态事件

public static event Action Updated;
public static void OnUpdated()
{
    if (Updated != null)
        Updated();
}

我发现使用lazy singletons 通常比使用静态类更好:

  • 在第一次调用 Instance 之前不会消耗内存;
  • 私有序列化/反序列化。

所以我将它们重构为单例,现在我有代码分析抱怨。

这样的事件显然得罪了CS1009,但我有一些疑问:

  • 对于静态事件sender 没有意义,为什么以及在什么情况下单例sender 有任何用处?我只能考虑反序列化,但它是一个内部类实现(而且类是密封的),所以我可以注意不使用事件,如果需要,我可以创建私有事件。

  • 创建e(派生自EventArgs)对于简单地传递参数来说是不必要的复杂化,我最讨厌的是将它移到命名空间级别,这是EventArgs唯一添加的东西(可能有用有时)是Empty,然后你有几十个课程...EventArgs。我能想到有时你需要 CancelHandled 机制,但我从来不需要它。

当使用事件时,每个人都期望(object sender, SomeEventArgs args),这是唯一的原因吗?

总而言之,这是我的主要问题(但我也希望澄清其他问题):CS1009 和单例,我应该修复事件还是简单地隐藏消息?

P.S.:相关主题:thisthisthis


我找到了this 的问题。根据event design guidelines,我必须使用Event<T>(忽略this问题),其中T基于EventArgs类。

关于静态事件中的sender

在静态事件中,sender 参数应为空。

这是一个设计指南,可能在我看来并不漂亮,但会受到其他任何人(正在阅读/维护我的代码的人)的欢迎。

就我而言,它违反了KISSYAGNI 的原则。而且越想越不知道该怎么办。

【问题讨论】:

  • 这取决于项目。如果它是一个巨大的 ddd 项目,我会明确地保留这些(发送者和事件参数),即使它们不被使用(以保留架构)。但如果它只是一个简单的控制台应用程序,那就另当别论了

标签: c# events


【解决方案1】:

我会修正你的错误。一般的设计准则确实是(object sender, EventArgs e) 签名。

这是一个约定,是关于代码一致性、代码可读性等的。遵循此模式将有助于其他人将处理程序附加到您的事件。

一些一般提示/答案:

  • 对于静态事件,您确实应该将null 用作sender(因为根据定义没有发送者实例)。
  • 如果您没有要为e 参数传递的内容,请使用EventArgs.Empty 而不是new EventArgs()null
  • 您可以使用EventHandlerEventHandler<T> 来简化事件的定义。
  • 为简单起见,如果您想将单个值传递给事件处理程序,则可以使用继承自 EventArgs 的自定义 EventArgs<T> 类。

如果您想使用带有Lazy<T> 定义的单例模式,这里有一个完整的示例。请注意没有事件是static,因此sender 参数包含对单例实例的引用:

public class EventArgs<T> : EventArgs
{
    public EventArgs(T value)
    {
        this.Value = value;
    }

    public T Value { get; set; }
}

public class EventArgs2 : EventArgs
{
    public int Value { get; set; }
}

internal static class Program
{
    private static void Main(string[] args)
    {
        Singleton.Instance.MyEvent += (sender, e) => Console.WriteLine("MyEvent with empty parameter");
        Singleton.Instance.MyEvent2 += (sender, e) => Console.WriteLine("MyEvent2 with parameter {0}", e.Value);
        Singleton.Instance.MyEvent3 += (sender, e) => Console.WriteLine("MyEvent3 with parameter {0}", e.Value);

        Singleton.Instance.Call();

        Console.Read();
    }
}

public sealed class Singleton
{
    private static readonly Lazy<Singleton> lazy = new Lazy<Singleton>(() => new Singleton());

    public static Singleton Instance { get { return lazy.Value; } }

    /// <summary>
    /// Prevents a default instance of the <see cref="Singleton"/> class from being created.
    /// </summary>
    private Singleton()
    {
    }

    /// <summary>
    /// Event without any associated data
    /// </summary>
    public event EventHandler MyEvent;

    /// <summary>
    /// Event with a specific class as associated data
    /// </summary>
    public event EventHandler<EventArgs2> MyEvent2;

    /// <summary>
    /// Event with a generic class as associated data
    /// </summary>
    public event EventHandler<EventArgs<int>> MyEvent3;

    public void Call()
    {
        if (this.MyEvent != null)
        {
            this.MyEvent(this, EventArgs.Empty);
        }

        if (this.MyEvent2 != null)
        {
            this.MyEvent2(this, new EventArgs2 { Value = 12 });
        }

        if (this.MyEvent3 != null)
        {
            this.MyEvent3(this, new EventArgs<int>(12));
        }

        Console.Read();
    }
}

编辑:

如果您需要传递两个值,您也可以构建一些EventArgs&lt;T1, T2&gt;。最终,EventArgs&lt;Tuple&lt;&gt;&gt; 也是可能的,但对于超过 2 个值,我将构建一个特定的 XXXEventArgs 类,因为它比 EventArgs&lt;T1, T2, T3&gt;.Value2EventArgs&lt;Tuple&lt;int, string, bool&gt;&gt;.Value.Item1 更容易阅读 XXXEventArgs.MyNamedBusinessProperty

关于 KISS/YAGNI:记住 (object sender, EventArgs e) 约定都是关于代码一致性的。如果某个开发人员使用您的代码将处理程序附加到您的某个事件,我可以向您保证,他会非常喜欢您的事件定义就像 BCL 中的任何其他事件定义一样自己,因此他立即知道如何正确使用您的代码。

除了代码一致性/可读性之外,还有其他优势:

我从EventArgs 继承了我的自定义XXXEventArgs 类,但您可以构建一些基础EventArgs 类并从它继承。例如,请参阅MouseEventArgs 以及从它继承的所有类。重用现有类比提供具有 5/6 相同属性的多个委托签名要好得多。例如:

public class MouseEventArgs : EventArgs
{
    public int X { get; set; }
    public int Y { get; set; }
}

public class MouseClickEventArgs : MouseEventArgs
{
    public int ButtonType { get; set; }
}

public class MouseDoubleClickEventArgs : MouseClickEventArgs
{
    public int TimeBetweenClicks { get; set; }
}

public class Test
{
    public event EventHandler<MouseClickEventArgs> ClickEvent;
    public event EventHandler<MouseDoubleClickEventArgs> DoubleClickEvent;
}

public class Test2
{
    public delegate void ClickEventHandler(int X, int Y, int ButtonType);
    public event ClickEventHandler ClickEvent;

    // See duplicated properties below =>
    public delegate void DoubleClickEventHandler(int X, int Y, int ButtonType, int TimeBetweenClicks);
    public event DoubleClickEventHandler DoubleClickEvent;
}

另外一点是,使用EventArgs 可以简化代码的可维护性。想象以下场景:

public MyEventArgs : EventArgs
{
    public string MyProperty { get; set; }
}
public event EventHandler<MyEventArgs> MyEvent;
...
if (this.MyEvent != null)
{
    this.MyEvent(this, new MyEventArgs { MyProperty = "foo" });
}
...
someInstance.MyEvent += (sender, e) => SomeMethod(e.MyProperty);

如果您想将一些 MyProperty2 属性添加到 MyEventArgs,您可以在不修改所有现有事件侦听器的情况下执行此操作:

public MyEventArgs : EventArgs
{
    public string MyProperty { get; set; }
    public string MyProperty2 { get; set; }
}

public event EventHandler<MyEventArgs> MyEvent;
...
if (this.MyEvent != null)
{
    this.MyEvent(this, new MyEventArgs { MyProperty = "foo", MyProperty2 = "bar" });
}
...
// I didn't change the event handler. If SomeMethod() doesn't need MyProperty2, everything is just fine already
someInstance.MyEvent += (sender, e) => SomeMethod(e.MyProperty);

【讨论】:

  • EventHandler&lt;EventArgs&lt;T&gt;&gt; 很酷,谢谢。但是对于多个参数,它会非常讨厌Tuple&lt;&gt; 对吗?你对 KISS 和 YAGNI 有意见吗?两人都说我不应该为我的Updated 事件做你建议的事情(没有发件人 - 因为static 或单例,并且没有参数)。你看,我正在寻找一种 final 论据来说服我,这是一种无与伦比的优势。
  • @Sinatr 我编辑了答案以提供更多信息
  • 我深信不疑。感谢所有的努力!
猜你喜欢
  • 2022-10-24
  • 2012-01-16
  • 1970-01-01
  • 1970-01-01
  • 2013-04-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-05-19
相关资源
最近更新 更多