Подпись события в .NET - Использование строго типизированного «отправителя»?

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

(1) Должен ли я рассмотреть возможность использования этого для моей собственной разработки, которая на 100% предназначена для внутренних целей.

(2) Является ли это концепцией, которую разработчики фреймворка могли бы рассмотреть для изменения или обновления?

Я думаю об использовании сигнатуры события, которая использует строго типизированный «отправитель», вместо того, чтобы вводить его как «объект», который является текущим шаблоном проектирования .NET. То есть вместо использования стандартной подписи события, которая выглядит следующим образом:

<code>class Publisher
{
    public event EventHandler<PublisherEventArgs> SomeEvent;
}
</code>

Я рассматриваю возможность использования подписи события, в которой используется строго типизированный «отправитель»; параметр, следующим образом:

Сначала определите & quot; StrongTypedEventHandler & quot ;:

<code>[SerializableAttribute]
public delegate void StrongTypedEventHandler<TSender, TEventArgs>(
    TSender sender,
    TEventArgs e
)
where TEventArgs : EventArgs;
</code>

Это не все, что отличается от действия & lt; TSender, TEventArgs & gt ;, но с использованиемStrongTypedEventHandlerмы обеспечиваем, чтобы TEventArgs происходил изSystem.EventArgs.

Далее, в качестве примера, мы можем использовать StrongTypedEventHandler в классе публикации следующим образом:

<code>class Publisher
{
    public event StrongTypedEventHandler<Publisher, PublisherEventArgs> SomeEvent;

    protected void OnSomeEvent()
    {
        if (SomeEvent != null)
        {
            SomeEvent(this, new PublisherEventArgs(...));
        }
    }
}
</code>

Приведенная выше схема позволит подписчикам использовать обработчик событий со строгой типизацией, который не требует приведения:

<code>class Subscriber
{
    void SomeEventHandler(Publisher sender, PublisherEventArgs e)
    {           
        if (sender.Name == "John Smith")
        {
            // ...
        }
    }
}
</code>

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

<code>class Subscriber
{
    void SomeEventHandler(object sender, PublisherEventArgs e)
    {           
        if (((Publisher)sender).Name == "John Smith")
        {
            // ...
        }
    }
}
</code>

То есть, если обработчику событий необходимо подписаться на события из разнородных (или, возможно, неизвестных) типов объектов, обработчик может напечатать «отправитель»; параметр как «объект» для того, чтобы обрабатывать все возможные объекты отправителя.

Кроме нарушения соглашения (что-то, что я не принимаю всерьез, поверьте мне), я не могу думать ни о каких минусах к этому.

Здесь могут быть некоторые проблемы соответствия CLS. Это действительно работает в Visual Basic .NET 2008 на 100% (я тестировал), но я полагаю, что более старые версии Visual Basic .NET до 2005 не имеют делегированных ковариации и контравариантности.[Edit: I have since tested this, and it is confirmed: VB.NET 2005 and below cannot handle this, but VB.NET 2008 is 100% fine. See "Edit #2", below.] Могут быть другие языки .NET, у которых также есть проблема с этим, я не могу быть уверен.

Но я не вижу себя разработчиком для какого-либо языка, кроме C # или Visual Basic .NET, и я не против ограничить его C # и VB.NET для .NET Framework 3.0 и выше. (Если честно, я не мог представить, что вернусь к 2.0.)

Кто-нибудь еще может подумать о проблеме с этим? Или это просто настолько нарушает условность, что заставляет желудки людей поворачиваться?

Вот некоторые ссылки, которые я нашел:

(1) Руководство по разработке мероприятий [MSDN 3.5]

(2) C # simple Event Raising - использование & # x201C; sender & # x201D; против пользовательских EventArgs [StackOverflow 2009]

(3) Шаблон подписи события в .net [StackOverflow 2008]

Меня интересует мнение всех и каждого по этому поводу ...

Заранее спасибо,

Майк

Edit #1: Это в ответ на Сообщение Томми Карлье:

Вот полный рабочий пример, который показывает, что как обработчики событий со строгой типизацией, так и текущие стандартные обработчики событий, которые используют «отправитель объекта»; Параметр может сосуществовать с этим подходом. Вы можете скопировать и вставить код и запустить его:

<code>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();
        }
    }
}
</code>

Edit #2: Это в ответ наЗаявление Эндрю Хэйра относительно ковариации и контравариантности и как это применяется здесь. Делегаты на языке C # имели ковариацию и контравариантность так долго, что это просто ощущается как «внутреннее», но это не так. Это может быть даже то, что включено в CLR, я не знаю, но Visual Basic .NET не получал возможности ковариации и контравариантности для своих делегатов до .NET Framework 3.0 (VB.NET 2008). В результате Visual Basic.NET для .NET 2.0 и ниже не сможет использовать этот подход.

Например, приведенный выше пример можно перевести на VB.NET следующим образом:

<code>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
</code>

VB.NET 2008 может работать на все 100%. Но я сейчас проверил его на VB.NET 2005, просто чтобы убедиться, и он не компилируется, заявив:

Method 'Public Sub SomeEventHandler(sender As Object, e As vbGenericEventHandling.GenericEventHandling.PublisherEventArgs)' does not have the same signature as delegate 'Delegate Sub StrongTypedEventHandler(Of TSender, TEventArgs As System.EventArgs)(sender As Publisher, e As PublisherEventArgs)'

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

Edit: Update #3

Хорошо, я использую это довольно успешно в настоящее время. Это действительно хорошая система. Я решил назвать мой «StrongTypedEventHandler» как "GenericEventHandler", определяется следующим образом:

<code>[SerializableAttribute]
public delegate void GenericEventHandler<TSender, TEventArgs>(
    TSender sender,
    TEventArgs e
)
where TEventArgs : EventArgs;
</code>

Помимо этого переименования, я реализовал его точно так, как описано выше.

Он отключается по правилу FxCop CA1009, которое гласит:

"By convention, .NET events have two parameters that specify the event sender and event data. Event handler signatures should follow this form: void MyEventHandler( object sender, EventArgs e). The 'sender' parameter is always of type System.Object, even if it is possible to employ a more specific type. The 'e' parameter is always of type System.EventArgs. Events that do not provide event data should use the System.EventHandler delegate type. Event handlers return void so that they can send each event to multiple target methods. Any value returned by a target would be lost after the first call."

Конечно, мы все это знаем и все равно нарушаем правила. (Все обработчики событий могут использовать стандартный «объект Sender» в своей подписи, если они предпочтительны в любом случае - это непреложное изменение.)

Так что использованиеSuppressMessageAttribute делает трюк:

<code>[SuppressMessage("Microsoft.Design", "CA1009:DeclareEventHandlersCorrectly",
    Justification = "Using strong-typed GenericEventHandler<TSender, TEventArgs> event handler pattern.")]
</code>

Я надеюсь, что этот подход станет стандартом в будущем. Это действительно работает очень хорошо.

Спасибо за все ваши мнения, ребята, я действительно ценю это ...

Майк

Ответы на вопрос(11)

Ваш ответ на вопрос