.NET EventHandlers - универсальный или нет? - PullRequest
20 голосов
/ 24 сентября 2008

Каждый раз, когда я начинаю глубоко в проекте 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 ... У кого-нибудь есть какие-либо идеи относящиеся к этому?

Ответы [ 9 ]

26 голосов
/ 25 сентября 2008

Делегат следующей формы был добавлен с .NET Framework 2.0

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

Ваш подход идет немного дальше, поскольку вы предоставляете готовую реализацию EventArgs с одним элементом данных, но в ней отсутствуют некоторые свойства исходной идеи:

  1. Вы не можете добавить больше свойств к данным события без изменения зависимого кода. Вам нужно будет изменить подпись делегата, чтобы предоставить больше данных подписчику события.
  2. Ваш объект данных является общим, но он также является "анонимным", и при чтении кода вам придется расшифровывать свойство "Item" из использования. Он должен быть назван в соответствии с данными, которые он предоставляет.
  3. Используя дженерики таким образом, вы не можете создать параллельную иерархию EventArgs, когда у вас есть иерархия базовых типов (элементов). Например. EventArgs не является базовым типом для EventArgs , даже если BaseType является базовым для DerivedType.

Итак, я думаю, что лучше использовать универсальный EventHandler , но все же есть пользовательские классы EventArgs, организованные в соответствии с требованиями модели данных. В Visual Studio и таких расширениях, как ReSharper, создание нескольких новых классов подобным образом.

9 голосов
/ 25 сентября 2008

Чтобы упростить объявление общего события, я создал для него пару фрагментов кода. Чтобы использовать их:

  • Скопируйте весь фрагмент.
  • Вставьте его в текстовый файл (например, в Блокнот).
  • Сохраните файл с расширением .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>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>

Вы можете следовать шаблону, чтобы создать их с любым количеством свойств.

8 голосов
/ 24 сентября 2008

Нет, я не думаю, что это неправильный подход. Я думаю, что это даже рекомендуется в [фантастической] книге Framework Design Guidelines . Я делаю то же самое.

3 голосов
/ 25 сентября 2008

Когда я впервые увидел этот маленький шаблон, я использовал Блок приложения составного пользовательского интерфейса из группы MS Patterns & Practices.

Это не бросает мне никакого красного флага; на самом деле это даже умный способ использовать дженерики, чтобы следовать правилу DRY .

3 голосов
/ 25 сентября 2008

Это правильная реализация. Он был добавлен в .NET Framework (mscorlib) с момента появления дженериков (2.0).

Подробнее о его использовании и реализации см. MSDN: http://msdn.microsoft.com/en-us/library/db0etb8x.aspx

2 голосов
/ 25 сентября 2008

Вы можете найти общий EventHandler на MSDN http://msdn.microsoft.com/en-us/library/db0etb8x.aspx

Я широко использовал универсальный EventHandler и смог предотвратить так называемый «Взрыв типов (классов)» Проект был уменьшен, и его было легче перемещать.

Создание нового интуитивного делегата для неуниверсального делегата EventHandler является болезненным и накладывается на существующие типы. Добавление "* EventHandler" к имени нового делегата, на мой взгляд, не очень помогает

2 голосов
/ 25 сентября 2008

С .NET 2.0

EventHandler<T>

было реализовано.

1 голос
/ 12 апреля 2010

Использовать универсальные экземпляры обработчиков событий

До .NET Framework 2.0 для передачи пользовательской информации в обработчик событий необходимо было объявить новый делегат, в котором указан класс, производный от класса System.EventArgs. Это больше не относится к .NET

Framework 2.0, в котором представлен делегат System.EventHandler ). Этот универсальный делегат позволяет любому классу, производному от EventArgs, использоваться с обработчиком событий.

1 голос
/ 24 сентября 2008

Я верю, что в последних версиях .NET определен именно такой обработчик событий. На мой взгляд, это большие пальцы.

/ EDIT

Первоначально не получил различия. Пока вы возвращаете класс, который наследует от EventArgs, которым вы являетесь, я не вижу проблемы. Я был бы обеспокоен, если бы вы не обернули результат по причинам ремонтопригодности. Я все еще говорю, что это выглядит хорошо для меня.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...