单身人士事件

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

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

我发现使用懒惰单例通常比使用静态类更好:

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

所以我重构那些单身人士,现在我有代码分析抱怨。

这样的事件显然冒犯了CS1009,但我有些疑惑:

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

  • 创建e (从EventArgs派生)是简单地传递参数的不必要的复杂性,我讨厌的最大部分是将其移动到命名空间级别, EventArgs添加的唯一东西(有时可能有用)是Empty然后你有几十个类...EventArgs 。 我可以想到有时你需要取消或处理机制,但它从来都不需要我。

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

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

PS:相关科目: 这个 , 这个和这个 。


我发现了这个问题。 根据事件设计指南,我必须使用Event (忽略这个问题),其中T基于EventArgs类。

关于静态事件中的sender

在静态事件上, sender参数应为null。

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

它对我来说打破了KISS和YAGNI的原则。 我想的越多,我越不确定该怎么做。

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

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

一些一般提示/答案:

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

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

 public class EventArgs : 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 lazy = new Lazy(() => new Singleton()); public static Singleton Instance { get { return lazy.Value; } } ///  /// Prevents a default instance of the  class from being created. ///  private Singleton() { } ///  /// Event without any associated data ///  public event EventHandler MyEvent; ///  /// Event with a specific class as associated data ///  public event EventHandler MyEvent2; ///  /// Event with a generic class as associated data ///  public event EventHandler> 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(12)); } Console.Read(); } } 

编辑:

如果需要传递两个值EventArgs也可以构建一些EventArgs 。 最终, EventArgs>也是可能的,但是对于超过2个值,我将构建一个特定的XXXEventArgs类,因为它比EventArgs.Value2EventArgs>.Value.Item1更容易读取XXXEventArgs.MyNamedBusinessProperty EventArgs>.Value.Item1

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

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

我从EventArgsinheritance了我自定义的XXXEventArgs类,但你可以构建一些基本的EventArgs类并从中inheritance。 例如,请参阅MouseEventArgs以及从中inheritance的所有类。 重用现有类比提供具有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 ClickEvent; public event EventHandler 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 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 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);