23

每次我开始深入 C# 项目时,我都会遇到很多真正只需要传递一个项目的事件。我坚持EventHandler/EventArgs练习,但我喜欢做的是:

public delegate void EventHandler<T>(object src, EventArgs<T> args);

public class EventArgs<T>: EventArgs {

  private T item;

  public EventArgs(T item) {
    this.item = item;
  }

  public T Item {
    get { return item; }
  }
}

以后,我可以拥有我的

public event EventHandler<Foo> FooChanged;

public event EventHandler<Bar> BarChanged;

然而,.NET 的标准似乎是EventArgs为每种类型的事件创建一个新的委托和子类。我的通用方法有问题吗?


编辑:这篇文章的原因是我刚刚在一个新项目中重新创建了它,并想确保它没问题。实际上,我在发布时正在重新创建它。我发现有一个泛型EventHandler<TEventArgs>,所以你不需要创建泛型委托,但你仍然需要泛型EventArgs<T>类,因为TEventArgs: EventArgs.
另一个编辑:内置解决方案的一个缺点(对我来说)是额外的冗长:

public event EventHandler<EventArgs<Foo>> FooChanged;

对比

public event EventHandler<Foo> FooChanged;

但是,客户注册您的活动可能会很痛苦,因为默认情况下会导入 System 命名空间,因此即使使用像 Resharper 这样的花哨工具,他们也必须手动查找您的命名空间......任何人都对此有任何想法?

4

9 回答 9

28

自 .NET Framework 2.0 起已添加以下形式的委托

public delegate void EventHandler<TArgs>(object sender, TArgs args) where TArgs : EventArgs

您的方法更进一步,因为您为带有单个数据项的 EventArgs 提供了开箱即用的实现,但它缺少原始想法的几个属性:

  1. 您不能在不更改相关代码的情况下向事件数据添加更多属性。您必须更改委托签名以向事件订阅者提供更多数据。
  2. 您的数据对象是通用的,但它也是“匿名的”,在阅读代码时,您必须从用法中破译“项目”属性。它应该根据它提供的数据来命名。
  3. 当您具有底层(项目)类型的层次结构时,以这种方式使用泛型您无法创建 EventArgs 的并行层次结构。例如,EventArgs<BaseType> 不是 EventArgs<DerivedType> 的基本类型,即使 BaseType 是 DerivedType 的基本类型。

所以,我认为最好使用通用的 EventHandler<T>,但仍然有自定义的 EventArgs 类,根据数据模型的要求进行组织。使用 Visual Studio 和 ReSharper 等扩展,只需几个命令即可创建这样的新类。

于 2008-09-24T20:13:36.127 回答
9

为了使通用事件声明更容易,我为它创建了几个代码片段。要使用它们:

  • 复制整个片段。
  • 将其粘贴到文本文件中(例如在记事本中)。
  • 使用 .snippet 扩展名保存文件。
  • 将 .snippet 文件放在适当的代码片段目录中,例如:

Visual Studio 2008\代码片段\Visual C#\我的代码片段

这是一个使用具有一个属性的自定义 EventArgs 类:

<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippets  xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
  <CodeSnippet Format="1.0.0">
    <Header>
      <Title>Generic event with one type/argument.</Title>
      <Shortcut>ev1Generic</Shortcut>
      <Description>Code snippet for event handler and On method</Description>
      <Author>Ryan Lundy</Author>
      <SnippetTypes>
        <SnippetType>Expansion</SnippetType>
      </SnippetTypes>
    </Header>
    <Snippet>
      <Declarations>
        <Literal>
          <ID>type</ID>
          <ToolTip>Type of the property in the EventArgs subclass.</ToolTip>
          <Default>propertyType</Default>
        </Literal>
        <Literal>
          <ID>argName</ID>
          <ToolTip>Name of the argument in the EventArgs subclass constructor.</ToolTip>
          <Default>propertyName</Default>
        </Literal>
        <Literal>
          <ID>propertyName</ID>
          <ToolTip>Name of the property in the EventArgs subclass.</ToolTip>
          <Default>PropertyName</Default>
        </Literal>
        <Literal>
          <ID>eventName</ID>
          <ToolTip>Name of the event</ToolTip>
          <Default>NameOfEvent</Default>
        </Literal>
      </Declarations>
      <Code Language="CSharp"><![CDATA[public class $eventName$EventArgs : System.EventArgs
      {
        public $eventName$EventArgs($type$ $argName$)
        {
          this.$propertyName$ = $argName$;
        }

        public $type$ $propertyName$ { get; private set; }
      }

      public event EventHandler<$eventName$EventArgs> $eventName$;
            protected virtual void On$eventName$($eventName$EventArgs e)
            {
                var handler = $eventName$;
                if (handler != null)
                    handler(this, e);
            }]]>
      </Code>
      <Imports>
        <Import>
          <Namespace>System</Namespace>
        </Import>
      </Imports>
    </Snippet>
  </CodeSnippet>
</CodeSnippets>

这是一个有两个属性的:

<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippets  xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
  <CodeSnippet Format="1.0.0">
    <Header>
      <Title>Generic event with two types/arguments.</Title>
      <Shortcut>ev2Generic</Shortcut>
      <Description>Code snippet for event handler and On method</Description>
      <Author>Ryan Lundy</Author>
      <SnippetTypes>
        <SnippetType>Expansion</SnippetType>
      </SnippetTypes>
    </Header>
    <Snippet>
      <Declarations>
        <Literal>
          <ID>type1</ID>
          <ToolTip>Type of the first property in the EventArgs subclass.</ToolTip>
          <Default>propertyType1</Default>
        </Literal>
        <Literal>
          <ID>arg1Name</ID>
          <ToolTip>Name of the first argument in the EventArgs subclass constructor.</ToolTip>
          <Default>property1Name</Default>
        </Literal>
        <Literal>
          <ID>property1Name</ID>
          <ToolTip>Name of the first property in the EventArgs subclass.</ToolTip>
          <Default>Property1Name</Default>
        </Literal>
        <Literal>
          <ID>type2</ID>
          <ToolTip>Type of the second property in the EventArgs subclass.</ToolTip>
          <Default>propertyType1</Default>
        </Literal>
        <Literal>
          <ID>arg2Name</ID>
          <ToolTip>Name of the second argument in the EventArgs subclass constructor.</ToolTip>
          <Default>property1Name</Default>
        </Literal>
        <Literal>
          <ID>property2Name</ID>
          <ToolTip>Name of the second property in the EventArgs subclass.</ToolTip>
          <Default>Property2Name</Default>
        </Literal>
        <Literal>
          <ID>eventName</ID>
          <ToolTip>Name of the event</ToolTip>
          <Default>NameOfEvent</Default>
        </Literal>
      </Declarations>
      <Code Language="CSharp">
        <![CDATA[public class $eventName$EventArgs : System.EventArgs
      {
        public $eventName$EventArgs($type1$ $arg1Name$, $type2$ $arg2Name$)
        {
          this.$property1Name$ = $arg1Name$;
          this.$property2Name$ = $arg2Name$;
        }

        public $type1$ $property1Name$ { get; private set; }
        public $type2$ $property2Name$ { get; private set; }
      }

      public event EventHandler<$eventName$EventArgs> $eventName$;
            protected virtual void On$eventName$($eventName$EventArgs e)
            {
                var handler = $eventName$;
                if (handler != null)
                    handler(this, e);
            }]]>
      </Code>
      <Imports>
        <Import>
          <Namespace>System</Namespace>
        </Import>
      </Imports>
    </Snippet>
  </CodeSnippet>
</CodeSnippets>

您可以按照该模式使用任意数量的属性创建它们。

于 2008-09-24T20:37:38.643 回答
8

不,我不认为这是错误的方法。我认为它甚至在 [fantastic] 书框架设计指南中被推荐。我做同样的事情。

于 2008-09-24T19:52:53.680 回答
3

这是正确的实现。自从泛型首次可用 (2.0) 以来,它已被添加到 .NET Framework (mscorlib) 中。

有关其用法和实现的更多信息,请参阅 MSDN:http: //msdn.microsoft.com/en-us/library/db0etb8x.aspx

于 2008-09-24T20:00:23.583 回答
3

第一次看到这个小模式时,我使用的是来自 MS Patterns & Practices 组的Composite UI Application block 。

它不会向我抛出任何危险信号;事实上,它甚至是一种利用泛型遵循DRY规则的聪明方法。

于 2008-09-24T20:05:43.323 回答
2

从 .NET 2.0 开始

EventHandler<T>

已实施。

于 2008-09-24T20:00:56.437 回答
2

您可以在 MSDN http://msdn.microsoft.com/en-us/library/db0etb8x.aspx上找到 Generic EventHandler

我一直在广泛使用通用 EventHandler,并且能够防止所谓的“类型(类)爆炸”项目保持更小,更容易导航。

为非通用 EventHandler 委托提供一个新的直观委托是痛苦的,并且与现有类型重叠在我看来,将“*EventHandler”附加到新的委托名称并没有多大帮助

于 2008-09-24T20:10:44.777 回答
1

我确实相信最新版本的 .NET 在其中定义了这样一个事件处理程序。就我而言,这是一个很大的赞许。

/编辑

最初没有得到那里的区别。只要你传回一个继承自 EventArgs 的类,你就是这样,我看不出有什么问题。如果您出于可维护性原因不包装结果,我会担心。我仍然说它对我来说看起来不错。

于 2008-09-24T19:57:51.593 回答
1

使用通用事件处理程序实例

在 .NET Framework 2.0 之前,为了将自定义信息传递给事件处理程序,必须声明一个新委托以指定派生自 System.EventArgs 类的类。这在 .NET 中不再适用

Framework 2.0,引入了 System.EventHandler<T>) 委托。此通用委托允许从 EventArgs 派生的任何类与事件处理程序一起使用。

于 2010-04-12T08:39:53.517 回答