Обработчики событий .NET - универсальные или нет?

StackOverflow https://stackoverflow.com/questions/129453

  •  02-07-2019
  •  | 
  •  

Вопрос

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

Однако клиентам может быть непросто зарегистрироваться на ваши мероприятия, потому что системное пространство имен импортируется по умолчанию, поэтому им приходится вручную искать ваше пространство имен, даже с помощью такого навороченного инструмента, как Resharper...У кого-нибудь есть какие-нибудь идеи на этот счет?

Это было полезно?

Решение

Начиная с .NET Framework 2.0, был добавлен делегат следующей формы

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

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

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

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

Другие советы

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

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

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

Нет, я не думаю, что это неправильный подход.Я думаю, это даже рекомендовано в [фантастической] книге Руководящие принципы по разработке Фреймворка.Я делаю то же самое.

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

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

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

Для меня это не является каким-либо тревожным сигналом ;на самом деле это даже разумный способ использования дженериков для следования СУХОЙ правило.

Начиная с .NET 2.0

EventHandler<T>

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

Вы можете найти Универсальный EventHandler в MSDN http://msdn.microsoft.com/en-us/library/db0etb8x.aspx

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

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

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

/РЕДАКТИРОВАТЬ

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top