.NET EventHandlers - 通用还是不通用?

时间:2008-09-24 19:50:42

标签: c# .net generics events

每当我深入开始一个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;

VS

public event EventHandler<Foo> FooChanged;

但是客户注册你的事件会很麻烦,因为系统名称空间是默认导入的,所以他们必须手动寻找你的命名空间,即使是像Resharper这样的花哨工具......任何人都有任何想法与此有关吗?

9 个答案:

答案 0 :(得分:26)

自.NET Framework 2.0

以来,已添加以下表单的委托
public delegate void EventHandler<TArgs>(object sender, TArgs args) where TArgs : EventArgs

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

  1. 您无法在不更改相关代码的情况下向事件数据添加更多属性。您必须更改委托签名以向事件订阅者提供更多数据。
  2. 您的数据对象是通用的,但它也是“匿名的”,在阅读代码时,您必须从用法中解读“Item”属性。它应该根据它提供的数据命名。
  3. 当您具有底层(项目)类型的层次结构时,以这种方式使用泛型,您无法建立EventArgs的并行层次结构。例如。 EventArgs的&LT;碱基类型&GT;不是EventArgs&lt; DerivedType&gt;的基本类型,即使BaseType是DerivedType的基础。
  4. 因此,我认为最好使用通用的EventHandler&lt; T&gt;,但仍然有自定义的EventArgs类,根据数据模型的要求进行组织。使用Visual Studio和ReSharper之类的扩展,创建新类只需要很少的命令。

答案 1 :(得分:9)

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

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

Visual Studio 2008 \ Code Snippets \ Visual C#\ My Code Snippets

这是一个使用带有一个属性的自定义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>Kyralessa</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>
        </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>Kyralessa</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>
    </Snippet>
  </CodeSnippet>
</CodeSnippets>

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

答案 2 :(得分:8)

不,我不认为这是错误的做法。我认为它甚至可以在[精彩]一书Framework Design Guidelines中推荐。我做同样的事情。

答案 3 :(得分:3)

这是正确的实施。它已被添加到.NET Framework(mscorlib),因为泛型首次出现(2.0)。

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

答案 4 :(得分:3)

我第一次看到这个小图案时,我正在使用Composite UI Application block来自MS Patterns&amp;实践小组。

它不会给我任何红旗;事实上,它甚至是一种利用泛型来遵循DRY规则的聪明方法。

答案 5 :(得分:2)

自.NET 2.0以来

  

EventHandler<T>

已经实施。

答案 6 :(得分:2)

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

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

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

答案 7 :(得分:1)

我确实认为.NET的最新版本中只有这样的事件处理程序。就我而言,这是一个大拇指。

/ EDIT

最初没有得到区别。只要你传回一个继承自EventArgs的类,你就不会发现问题。如果由于可维护性原因而没有包装结果,我会担心。我仍然说它看起来不错。

答案 8 :(得分:1)

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

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

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