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

108

Я полностью понимаю, что то, что я предлагаю, не соответствует рекомендациям .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]

(2) Простое создание событий C # - использование «отправителя» по сравнению с настраиваемыми EventArgs [StackOverflow 2009]

(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.")]

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

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

Майк

Майк Розенблюм
источник
6
Сделай это. (Не думайте, что это оправдывает ответ.)
Конрад Рудольф
1
Мои аргументы на самом деле не были направлены на вас: конечно, вы должны делать это в своих собственных проектах. Это были аргументы, почему это могло не работать в BCL.
Tommy Carlier
3
Чувак, я бы хотел, чтобы мой проект делал это с самого начала, я ненавижу бросать отправителя.
Matt H,
7
Теперь ЭТО вопрос. Видите, ребята? Не один из этих oh hi this my hom work solve it plz :code dump:вопросов размером с твит , а вопрос, на котором мы учимся .
Камило Мартин
3
Еще одно предложение, просто назовите его EventHandler<,>чем GenericEventHandler<,>. В EventHandler<>BCL уже есть общий, который называется просто EventHandler. Итак, EventHandler - более распространенное имя, и делегаты поддерживают перегрузку типов
nawfal

Ответы:

25

Кажется, Microsoft подхватила это, поскольку аналогичный пример теперь есть на MSDN:

Общие делегаты

Bas
источник
2
+1 Ах, отлично. Они действительно это заметили. Это хорошо. Однако я надеюсь, что они сделают этот шаблон узнаваемым в VS IDE, потому что, как сейчас, более неудобно использовать этот шаблон с точки зрения IntelliSense и т. Д.
Майк Розенблюм
13

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

BFree
источник
1
Я уверен, что причина именно в этом. Однако теперь, когда в новых версиях языка есть контравариантность, чтобы справиться с этим, похоже, что они должны иметь возможность справляться с этим способом обратной совместимости. Предыдущие обработчики, которые использовали «объект-отправитель», не сломались бы. Но я не уверен, что это не так для старых языков и может быть неверно для некоторых текущих языков .NET.
Майк Розенблюм,
13

Среда выполнения Windows (WinRT) представляет TypedEventHandler<TSender, TResult>делегата, который делает то же самое, что и вы StrongTypedEventHandler<TSender, TResult>, но, очевидно, без ограничения на TResultпараметр типа:

public delegate void TypedEventHandler<TSender, TResult>(TSender sender,
                                                         TResult args);

Документация MSDN находится здесь .

Пьер Арно
источник
1
Ах, приятно видеть, что есть прогресс ... Интересно, почему TResult не ограничен наследованием от класса EventArgs. Базовый класс EventArgs практически пуст; может они отходят от этого ограничения?
Майк Розенблюм
Это может быть недосмотр команды дизайнеров; кто знает.
Pierre Arnaud
ну, события работают нормально без использования EventArgs, это просто условность
Себастьян
3
В документации TypedEventHandler специально указано, что argsбудет, nullесли нет данных о событии, поэтому похоже, что они уходят от использования по существу пустого объекта по умолчанию. Я предполагаю, что первоначальная идея заключалась в том, что метод со вторым параметром типа EventArgsмог бы обрабатывать любое событие, потому что типы всегда будут совместимы. Теперь они, вероятно, понимают, что возможность обрабатывать несколько разных событий одним методом не так уж и важна.
jmcilhinney
1
Это не похоже на оплошность. Ограничение также было снято с делегата System.EventHandler <TEventArgs>. referenceource.microsoft.com/#mscorlib/system/…
colton7909, 09
5

Я не согласен со следующими утверждениями:

  • Я считаю, что более старые версии Visual Basic .NET до 2005 года не имеют ковариации делегатов и контравариантности.
  • Я прекрасно понимаю, что это граничит с богохульством.

Прежде всего, все, что вы здесь сделали, не имеет ничего общего с ковариацией или контравариантностью. ( Изменить: предыдущее утверждение неверно, для получения дополнительной информации см. Ковариацию и контравариантность в делегатах ) Это решение будет отлично работать во всех версиях CLR 2.0 и выше (очевидно, это не будет работать в приложении CLR 1.0, поскольку оно использует дженерики).

Во-вторых, я категорически не согласен с тем, что ваша идея граничит с «богохульством», поскольку это замечательная идея.

Эндрю Хэйр
источник
2
Привет, Эндрю, спасибо за большой палец вверх! Учитывая уровень вашей репутации, это действительно много значит для меня ... По вопросу ковариации / контравариантности: если делегат, предоставленный подписчиком, не совпадает в точности с подписью события издателя, тогда задействованы ковариация и контравариантность. В C # всегда была ковариация и контравариантность делегатов, поэтому он кажется внутренним, но VB.NET не имел ковариации и контравариантности делегатов до .NET 3.0. Следовательно, VB.NET для .NET 2.0 и ниже не сможет использовать эту систему. (См. Пример кода, который я добавил в разделе «Правка № 2» выше.)
Майк Розенблюм,
@Mike - Мои извинения, вы на 100% правы! Я отредактировал свой ответ, чтобы отразить вашу точку зрения :)
Эндрю Хейр,
4
Ах, интересно! Кажется, что ковариация / контравариантность делегата является частью CLR, но (по неизвестным мне причинам) она не была представлена ​​VB.NET до самой последней версии. Вот статья Франческо Балена, в которой показано, как можно добиться дисперсии делегата с помощью Reflection, если это не разрешено самим языком: dotnet2themax.com/blogs/fbalena/… .
Mike Rosenblum
1
@Mike - Всегда интересно узнать о том, что поддерживает CLR, но не поддерживается ни на одном из языков .NET.
Эндрю Хэйр,
5

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

[Serializable]
public delegate void TypedEventHandler<in TSender, in TEventArgs>(
    TSender sender,
    TEventArgs e
) where TEventArgs : EventArgs;

Это, по-видимому, лучший способ продвижения вперед, учитывая использование имени TypedEventHandler в WinRT.

Инвернесс
источник
Зачем добавлять общее ограничение для TEventArgs? Он был удален из EventHandler <> и TypedEventHandler <,>, потому что на самом деле это не имело смысла.
Майк Мариновски
2

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

Отавио Десио
источник
Возможно, вы правы ... С другой стороны, я думаю, что это просто "стандарт" и, возможно, вовсе не техническая проблема. То есть эта возможность может присутствовать во всех текущих языках .NET, я не знаю. Я знаю, что C # и VB.NET справятся с этим. Однако я не уверен, насколько широко это работает на всех текущих языках .NET ... Но поскольку он работает на C # и VB.NET, и все здесь так поддерживают, я думаю, что, скорее всего, я это сделаю. :-)
Майк Розенблюм
2

Насколько я понимаю, поле «Отправитель» всегда должно относиться к объекту, который содержит подписку на событие. Если бы у меня были мои druthers, также было бы поле, содержащее информацию, достаточную для отмены подписки на событие, если оно станет необходимым (*) (рассмотрим, например, регистратор изменений, который подписывается на события 'collection-changed'; он состоит из двух частей , одна из которых выполняет фактическую работу и содержит фактические данные, а другая предоставляет оболочку общедоступного интерфейса, основная часть может содержать слабую ссылку на часть оболочки. Если часть оболочки будет собираться сборщиком мусора, это будет означать никто больше не интересовался собираемыми данными, и регистратор изменений, таким образом, должен отказаться от подписки на любое полученное событие).

Поскольку возможно, что объект может отправлять события от имени другого объекта, я вижу некоторую потенциальную полезность наличия поля «отправитель», имеющего тип объекта, и наличия поля, производного от EventArgs, которое содержит ссылку на объект, который должен действовать. Однако бесполезность поля «отправитель», вероятно, ограничена тем фактом, что у объекта нет чистого способа отписаться от неизвестного отправителя.

(*) На самом деле, более простой способ обработки отказа от подписки - иметь тип делегата многоадресной рассылки для функций, возвращающих логическое значение; если функция, вызываемая таким делегатом, возвращает True, делегат будет исправлен для удаления этого объекта. Это означало бы, что делегаты больше не будут по-настоящему неизменными, но должна быть возможность произвести такое изменение поточно-безопасным способом (например, обнуляя ссылку на объект и заставляя код делегата многоадресной рассылки игнорировать любые встроенные ссылки на нулевые объекты). В этом сценарии попытка публикации и событие для удаленного объекта могут быть обработаны очень чисто, независимо от того, откуда пришло событие.

суперкар
источник
2

Оглядываясь назад на богохульство как на единственную причину, по которой отправитель является типом объекта (если не учитывать проблемы с контравариантностью в коде VB 2005, что является ошибкой Microsoft, IMHO), может ли кто-нибудь предложить хотя бы теоретический мотив для прибивания второго аргумента к типу EventArgs. Если пойти еще дальше, есть ли веская причина соответствовать рекомендациям и соглашениям Microsoft в данном конкретном случае?

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

[Пример 1]

public delegate void ConnectionEventHandler(Server sender, Connection connection);

public partial class Server
{
    protected virtual void OnClientConnected(Connection connection)
    {
        if (ClientConnected != null) ClientConnected(this, connection);
    }

    public event ConnectionEventHandler ClientConnected;
}

[Пример 2]

public delegate void ConnectionEventHandler(object sender, ConnectionEventArgs e);

public class ConnectionEventArgs : EventArgs
{
    public Connection Connection { get; private set; }

    public ConnectionEventArgs(Connection connection)
    {
        this.Connection = connection;
    }
}

public partial class Server
{
    protected virtual void OnClientConnected(Connection connection)
    {
        if (ClientConnected != null) ClientConnected(this, new ConnectionEventArgs(connection));
    }

    public event ConnectionEventHandler ClientConnected;
}
Lu4
источник
2
Да, создание отдельного класса, наследуемого от System.EventArgs, может показаться неинтуитивным и представляет собой дополнительную работу, но для этого есть очень веская причина. Если вам никогда не нужно менять код, тогда ваш подход подойдет. Но в действительности вам может потребоваться расширить функциональность события в будущей версии и добавить свойства в аргументы события. В вашем сценарии вам нужно будет добавить дополнительные перегрузки или дополнительные параметры в подпись обработчика событий. Это подход, используемый в VBA и устаревшем VB 6.0, который работает, но на практике немного уродлив.
Майк Розенблюм,
1
Однако, унаследовав от EventArgs, будущая версия может наследовать от вашего более старого класса аргументов событий и расширять его. Все старые вызывающие объекты могут по-прежнему работать точно так же, как есть, работая с базовым классом вашего нового класса аргументов события. Очень чистый. Больше работы для вас, но чище для всех вызывающих абонентов, которые зависят от вашей библиотеки.
Майк Розенблюм,
Его даже не нужно наследовать, вы можете просто добавить дополнительные функции прямо в свой класс аргументов событий, и он продолжит нормально работать. Тем не менее, ограничение прикрепления аргументов к аргументам событий было удалено, потому что оно не имело особого смысла для многих сценариев, т.е. когда вы знаете, что вам никогда не потребуется расширять функциональность конкретного события или когда все, что вам нужно, это тип значения arg в приложениях, очень чувствительных к производительности.
Майк Мариновски
1

В текущей ситуации (отправитель - объект) вы можете легко прикрепить метод к нескольким событиям:

button.Click += ClickHandler;
label.Click += ClickHandler;

void ClickHandler(object sender, EventArgs e) { ... }

Если бы отправитель был универсальным, цель события щелчка была бы не типа Button или Label, а типа Control (поскольку событие определено в Control). Таким образом, некоторые события в классе Button будут иметь цель типа Control, а другие - другие типы целей.

Томми Карлье
источник
2
Томми, вы можете сделать то же самое с системой, которую я предлагаю. Вы по-прежнему можете использовать стандартный обработчик событий, у которого есть параметр «отправитель объекта» для обработки этих строго типизированных событий. (См. Пример кода, который я добавил к исходному сообщению.)
Майк Розенблюм,
Да, я согласен, это хорошо, что стандартные события .NET принимаются!
Lu4
1

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

Если вы действительно хотите внести это изменение для общедоступного API, вы можете подумать о создании собственного базового класса EvenArgs. Что-то вроде этого:

public class DataEventArgs<TSender, TData> : EventArgs
{
    private readonly TSender sender, TData data;

    public DataEventArgs(TSender sender, TData data)
    {
        this.sender = sender;
        this.data = data;
    }

    public TSender Sender { get { return sender; } }
    public TData Data { get { return data; } }
}

Тогда вы можете объявить свои события вот так

public event EventHandler<DataEventArgs<MyClass, int>> SomeIndexSelected;

И такие методы:

private void HandleSomething(object sender, EventArgs e)

по-прежнему сможет подписаться.

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

Эта последняя строка заставила меня немного задуматься ... Вы действительно должны иметь возможность реализовать то, что вы предлагаете, без нарушения каких-либо внешних функций, поскольку среда выполнения не имеет проблем с понижением параметров. Я все равно склоняюсь к DataEventArgsрешению (лично). Я бы сделал это, но зная, что это избыточно, поскольку отправитель хранится в первом параметре и как свойство аргументов события.

Одно из преимуществ использования DataEventArgsзаключается в том, что вы можете связывать события, изменяя отправителя (для представления последнего отправителя), в то время как EventArgs сохраняет исходного отправителя.

Майкл Медоуз
источник
Привет, Майкл, это отличная альтернатива. Мне это нравится. Однако, как вы упомянули, дважды эффективно передавать параметр «отправитель». Подобный подход обсуждается здесь: stackoverflow.com/questions/809609/… , и, похоже, все согласны с тем, что он слишком нестандартен. Вот почему я не решился предложить здесь идею строго типизированного «отправителя». (Кажется, что меня хорошо приняли, так что я доволен.)
Майк Розенблюм
1

Действуй. Для кода, не основанного на компонентах, я часто упрощаю сигнатуры событий, чтобы они были просто

public event Action<MyEventType> EventName

откуда MyEventTypeне наследуется EventArgs. Зачем беспокоиться, если я никогда не собираюсь использовать какие-либо члены EventArgs.

Скотт Вайнштейн
источник
1
Согласен! Почему мы должны чувствовать себя обезьянами?
Lu4
1
+1-е изд, я тоже этим пользуюсь. Иногда простота побеждает! Или даже event Action<S, T>и event Action<R, S, T>т. Д. У меня есть метод расширения к Raiseним поточно-безопасный :)
nawfal