Я полностью понимаю, что то, что я предлагаю, не соответствует рекомендациям .NET, и, следовательно, это, вероятно, плохая идея по этой причине. Однако я хотел бы рассмотреть это с двух возможных точек зрения:
(1) Должен ли я использовать это для моей собственной работы по разработке, что составляет 100% для внутренних целей.
(2) Является ли это концепцией, которую дизайнеры фреймворка могли рассмотреть или изменить?
Я подумываю об использовании сигнатуры события, которая использует синтаксический "отправитель", вместо того, чтобы вводить его как "объект", который является текущим шаблоном проектирования .NET. То есть вместо использования стандартной сигнатуры события, которая выглядит так:
class Publisher
{
public event EventHandler<PublisherEventArgs> SomeEvent;
}
Я рассматриваю использование сигнатуры события, которая использует параметр "sender" с сильным типом, следующим образом:
Сначала определите "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")
{
// ...
}
}
}
То есть, если обработчик события должен был подписаться на события из разрозненных (или, возможно, неизвестных) типов объектов, обработчик мог бы ввести параметр "sender" как "объект", чтобы обрабатывать всю ширину потенциальных объектов отправителя.
Помимо нарушения соглашения (что-то, что я не воспринимаю легко, поверьте мне), я не могу придумать никаких недостатков.
Здесь могут быть некоторые проблемы с соблюдением CLS. Это выполняется в Visual Basic.NET 2008 на 100% отлично (я тестировал), но я считаю, что более старые версии Visual Basic.NET до 2005 года не имеют ковариации и контравариантности делегатов. [Изменить: с тех пор я тестировал это, и это подтверждено: VB.NET 2005 и ниже не могут справиться с этим, но VB.NET 2008 на 100% лучше. См. "Редактировать # 2" ниже.] Могут быть и другие языки .NET, у которых также есть проблемы с этим, я не могу быть уверен.
Но я не вижу, что я разрабатываю для любого языка, отличного от С# или Visual Basic.NET, и я не против ограничивать его на С# и VB.NET для .NET Framework 3.0 и выше. (Я не мог себе представить, чтобы вернуться к версии 2.0 на данный момент, если честно.)
Может ли кто-нибудь еще подумать о проблеме с этим? Или это просто нарушает конвенцию настолько, что это заставляет людей вращаться?
Вот некоторые связанные ссылки, которые я нашел:
(1) Руководство по дизайну событий [MSDN 3.5]
Меня интересует мнение всех и каждого по этому поводу...
Спасибо заранее,
Mike
Изменить # 1: Это ответ на сообщение Tommy Carlier:
Здесь приведен полный рабочий пример, который показывает, что как сильные типизированные обработчики событий, так и текущие стандартные обработчики событий, которые используют параметр "отправитель объекта", могут сосуществовать с этим подходом. Вы можете скопировать-вставить в код и запустить его:
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: Это ответ на выражение Эндрю Харе относительно ковариации и контравариантности и того, как это применимо здесь. Делегаты на языке С# имели ковариацию и контравариантность так долго, что они просто чувствовали себя "неотъемлемыми", но это не так. Возможно, это даже может быть что-то, что включено в 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 В виде vbGenericEventHandling.GenericEventHandling.PublisherEventArgs)" не имеет такой же делегат ' StrongTypedEventHandler (из TSender, TEventArgs As System.EventArgs) (отправитель Как издатель, e As PublisherEventArgs)
В принципе, делегаты являются инвариантными в версиях VB.NET 2005 и ниже. Я действительно думал об этой идее пару лет назад, но неспособность VB.NET справиться с этим беспокоила меня... Но я теперь полностью перешел на С#, и VB.NET теперь может ее обрабатывать, поэтому, ну, следовательно, этот пост.
Изменить: Обновить # 3
Хорошо, я использовал это довольно успешно некоторое время. Это действительно хорошая система. Я решил назвать мой "StrongTypedEventHandler" как "GenericEventHandler", который определяется следующим образом:
[SerializableAttribute]
public delegate void GenericEventHandler<TSender, TEventArgs>(
TSender sender,
TEventArgs e
)
where TEventArgs : EventArgs;
Помимо этого переименования, я реализовал его точно так, как описано выше.
Это срабатывает по правилу FxCop CA1009, в котором говорится:
"По соглашению, события .NET имеют два параметры, указывающие событие данных отправителя и событий. Обработчик события подписи должны следовать этой форме: void MyEventHandler (отправитель объекта, EventArgs e). Параметр 'sender' всегда имеет тип System.Object, даже если можно использовать более определенного типа. Параметр 'e' всегда типа System.EventArgs. События, которые не предоставляют данные о событиях следует использовать System.EventHandler тип делегата. Обработчики событий возвращаются void, чтобы они могли отправлять каждое событие к нескольким целевым методам. Любое значение возвращенный мишенью, будет потерян после первого вызова.
Конечно, мы все это знаем и все равно нарушаем правила. (Все обработчики событий могут использовать стандартный "отправитель объекта" в своей подписи, если это предпочтительнее в любом случае - это непереломное изменение.)
Таким образом, использование SuppressMessageAttribute
делает трюк:
[SuppressMessage("Microsoft.Design", "CA1009:DeclareEventHandlersCorrectly",
Justification = "Using strong-typed GenericEventHandler<TSender, TEventArgs> event handler pattern.")]
Я надеюсь, что этот подход станет стандартом в какой-то момент в будущем. Это действительно работает очень хорошо.
Спасибо за все ваше мнение, ребята, я очень ценю это...
Mike