Я полностью понимаю, что то, что я предлагаю, не соответствует рекомендациям .NET и, следовательно, является плохой идеей только по этой причине. Однако я хотел бы рассмотреть это с двух возможных точек зрения:
(1) Следует ли мне рассмотреть возможность использования этого для моей собственной разработки, которая на 100% предназначена для внутренних целей.
(2) Разработчики фреймворка могли бы рассмотреть возможность изменения или обновления этой концепции?
Я подумываю об использовании сигнатуры события, которая использует строго типизированный «отправитель», вместо того, чтобы вводить его как «объект», что является текущим шаблоном проектирования .NET. То есть вместо использования стандартной сигнатуры события, которая выглядит так:
class Publisher
{
public event EventHandler<PublisherEventArgs> SomeEvent;
}
Я рассматриваю возможность использования сигнатуры события, которая использует строго типизированный параметр отправителя, а именно:
Сначала определите StrongTypedEventHandler:
[SerializableAttribute]
public delegate void StrongTypedEventHandler<TSender, TEventArgs>(
TSender sender,
TEventArgs e
)
where TEventArgs : EventArgs;
Это не так уж и отличается от Action <TSender, TEventArgs>, но, используя StrongTypedEventHandler
, мы обеспечиваем, что TEventArgs является производным от System.EventArgs
.
Далее, в качестве примера, мы можем использовать StrongTypedEventHandler в классе публикации следующим образом:
class Publisher
{
public event StrongTypedEventHandler<Publisher, PublisherEventArgs> SomeEvent;
protected void OnSomeEvent()
{
if (SomeEvent != null)
{
SomeEvent(this, new PublisherEventArgs(...));
}
}
}
Вышеупомянутая схема позволит подписчикам использовать обработчик событий со строгой типизацией, который не требует преобразования типов:
class Subscriber
{
void SomeEventHandler(Publisher sender, PublisherEventArgs e)
{
if (sender.Name == "John Smith")
{
// ...
}
}
}
Я полностью понимаю, что это противоречит стандартному шаблону обработки событий .NET; однако имейте в виду, что контравариантность позволит подписчику при желании использовать традиционную сигнатуру обработки событий:
class Subscriber
{
void SomeEventHandler(object sender, PublisherEventArgs e)
{
if (((Publisher)sender).Name == "John Smith")
{
// ...
}
}
}
То есть, если обработчику событий необходимо подписаться на события от разнородных (или, возможно, неизвестных) типов объектов, обработчик может ввести параметр «отправитель» как «объект», чтобы обрабатывать весь спектр потенциальных объектов-отправителей.
Кроме нарушения условностей (к чему я не отношусь легкомысленно, поверьте мне), я не могу думать о каких-либо недостатках этого.
Здесь могут быть некоторые проблемы с соблюдением CLS. Это действительно работает в Visual Basic .NET 2008 на 100% нормально (я тестировал), но я считаю, что более старые версии Visual Basic .NET до 2005 не имеют ковариации делегатов и контравариантности. [Изменить: с тех пор я тестировал это, и это подтверждено: VB.NET 2005 и ниже не могут справиться с этим, но VB.NET 2008 на 100% хорош. См. «Edit # 2» ниже.] Могут быть другие языки .NET, у которых тоже есть проблемы с этим, я не уверен.
Но я не вижу себя разработчиком для какого-либо языка, кроме C # или Visual Basic .NET, и не возражаю ограничить его C # и VB.NET для .NET Framework 3.0 и выше. (Честно говоря, я не мог себе представить, что вернусь к 2.0 в тот момент.)
Кто-нибудь еще может подумать о проблеме с этим? Или это просто настолько противоречит общепринятым нормам, что заставляет людей перевернуться?
Вот несколько ссылок, которые я нашел:
(1) Рекомендации по организации мероприятий [MSDN 3.5]
(3) Шаблон подписи событий в .NET [StackOverflow 2008]
Меня интересует мнение каждого и каждого по этому поводу ...
Заранее спасибо,
Майк
Редактировать # 1: это ответ на сообщение Томми Карлье :
Вот полный рабочий пример, который показывает, что как обработчики событий со строгой типизацией, так и текущие стандартные обработчики событий, которые используют параметр «отправитель объекта», могут сосуществовать с этим подходом. Вы можете скопировать и вставить код и запустить его:
namespace csScrap.GenericEventHandling
{
class PublisherEventArgs : EventArgs
{
// ...
}
[SerializableAttribute]
public delegate void StrongTypedEventHandler<TSender, TEventArgs>(
TSender sender,
TEventArgs e
)
where TEventArgs : EventArgs;
class Publisher
{
public event StrongTypedEventHandler<Publisher, PublisherEventArgs> SomeEvent;
public void OnSomeEvent()
{
if (SomeEvent != null)
{
SomeEvent(this, new PublisherEventArgs());
}
}
}
class StrongTypedSubscriber
{
public void SomeEventHandler(Publisher sender, PublisherEventArgs e)
{
MessageBox.Show("StrongTypedSubscriber.SomeEventHandler called.");
}
}
class TraditionalSubscriber
{
public void SomeEventHandler(object sender, PublisherEventArgs e)
{
MessageBox.Show("TraditionalSubscriber.SomeEventHandler called.");
}
}
class Tester
{
public static void Main()
{
Publisher publisher = new Publisher();
StrongTypedSubscriber strongTypedSubscriber = new StrongTypedSubscriber();
TraditionalSubscriber traditionalSubscriber = new TraditionalSubscriber();
publisher.SomeEvent += strongTypedSubscriber.SomeEventHandler;
publisher.SomeEvent += traditionalSubscriber.SomeEventHandler;
publisher.OnSomeEvent();
}
}
}
Изменить № 2: Это ответ на заявление Эндрю Хейра относительно ковариации и контравариантности и того, как это применимо здесь. Делегаты на языке C # так долго обладали ковариацией и контравариантностью, что это просто кажется «внутренним», но это не так. Возможно, это даже что-то, что включено в CLR, я не знаю, но Visual Basic .NET не получил возможности ковариации и контравариантности для своих делегатов до .NET Framework 3.0 (VB.NET 2008). В результате Visual Basic.NET для .NET 2.0 и ниже не сможет использовать этот подход.
Например, приведенный выше пример можно перевести на VB.NET следующим образом:
Namespace GenericEventHandling
Class PublisherEventArgs
Inherits EventArgs
' ...
' ...
End Class
<SerializableAttribute()> _
Public Delegate Sub StrongTypedEventHandler(Of TSender, TEventArgs As EventArgs) _
(ByVal sender As TSender, ByVal e As TEventArgs)
Class Publisher
Public Event SomeEvent As StrongTypedEventHandler(Of Publisher, PublisherEventArgs)
Public Sub OnSomeEvent()
RaiseEvent SomeEvent(Me, New PublisherEventArgs)
End Sub
End Class
Class StrongTypedSubscriber
Public Sub SomeEventHandler(ByVal sender As Publisher, ByVal e As PublisherEventArgs)
MessageBox.Show("StrongTypedSubscriber.SomeEventHandler called.")
End Sub
End Class
Class TraditionalSubscriber
Public Sub SomeEventHandler(ByVal sender As Object, ByVal e As PublisherEventArgs)
MessageBox.Show("TraditionalSubscriber.SomeEventHandler called.")
End Sub
End Class
Class Tester
Public Shared Sub Main()
Dim publisher As Publisher = New Publisher
Dim strongTypedSubscriber As StrongTypedSubscriber = New StrongTypedSubscriber
Dim traditionalSubscriber As TraditionalSubscriber = New TraditionalSubscriber
AddHandler publisher.SomeEvent, AddressOf strongTypedSubscriber.SomeEventHandler
AddHandler publisher.SomeEvent, AddressOf traditionalSubscriber.SomeEventHandler
publisher.OnSomeEvent()
End Sub
End Class
End Namespace
VB.NET 2008 может работать на 100% нормально. Но теперь я протестировал его на VB.NET 2005, просто чтобы быть уверенным, и он не компилируется, заявив:
Метод 'Public Sub SomeEventHandler (отправитель как объект, e As vbGenericEventHandling.GenericEventHandling.PublisherEventArgs)' не имеет той же подписи, что и делегат 'Delegate Sub StrongTypedEventHandler (Of TSender, TEventArgs As System.EventArgs Publisher) (отправитель как System.EventArgs Publisher) (отправитель As '
По сути, делегаты инвариантны в версиях VB.NET 2005 и ниже. Я действительно подумал об этой идее пару лет назад, но неспособность VB.NET справиться с этим меня беспокоила ... Но теперь я полностью перешел на C #, и VB.NET теперь может справиться с этим, так что, ну, следовательно эта почта.
Изменить: Обновление # 3
Хорошо, я довольно успешно использую это уже некоторое время. Это действительно хорошая система. Я решил назвать свой StrongTypedEventHandler как GenericEventHandler, определяемый следующим образом:
[SerializableAttribute]
public delegate void GenericEventHandler<TSender, TEventArgs>(
TSender sender,
TEventArgs e
)
where TEventArgs : EventArgs;
Помимо этого переименования, я реализовал его точно так, как описано выше.
Он нарушает правило CA1009 FxCop, которое гласит:
«По соглашению, у событий .NET есть два параметра, которые определяют отправителя события и данные события. Сигнатуры обработчика событий должны иметь следующую форму: void MyEventHandler (отправитель объекта, EventArgs e). Параметр« отправитель »всегда имеет тип System.Object, даже если можно использовать более конкретный тип. Параметр e всегда имеет тип System.EventArgs. События, которые не предоставляют данные о событиях, должны использовать тип делегата System.EventHandler. Обработчики событий возвращают void, чтобы они могли отправлять каждое событие для нескольких целевых методов. Любое значение, возвращаемое целью, будет потеряно после первого вызова ".
Конечно, мы все это знаем и все равно нарушаем правила. (Все обработчики событий могут использовать стандартный «отправитель объекта» в своей подписи, если это необходимо, в любом случае - это неизменное изменение.)
Итак, использование a SuppressMessageAttribute
делает свое дело:
[SuppressMessage("Microsoft.Design", "CA1009:DeclareEventHandlersCorrectly",
Justification = "Using strong-typed GenericEventHandler<TSender, TEventArgs> event handler pattern.")]
Я надеюсь, что в какой-то момент в будущем этот подход станет стандартом. Это действительно очень хорошо работает.
Спасибо за ваше мнение, ребята, я очень ценю это ...
Майк
oh hi this my hom work solve it plz :code dump:
вопросов размером с твит , а вопрос, на котором мы учимся .
EventHandler<,>
чем GenericEventHandler<,>
. В EventHandler<>
BCL уже есть общий, который называется просто EventHandler. Итак, EventHandler - более распространенное имя, и делегаты поддерживают перегрузку типов