Підпис події в .NET - Використання сильно набраного "Відправника"?


106

Я цілком усвідомлюю, що те, що я пропоную, не відповідає вказівкам .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% добре. Див. "Редагувати №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 як vbGenericEventHandling.GenericEventHandling.PublisherEventArgs)' не має такої ж підпису, як делегат 'Delegate Sub StrongTypedEventHandler (TSender, TEventArgs As System.EventArgs) (відправник видавець, видавець видавець '

В основному, делегати є інваріантними у версіях 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 мають два параметри, які задають відправника події та дані події. Підписи обробника події повинні відповідати цій формі: недійсний MyEventHandler (відправник об'єкта, EventArgs e). Параметр" відправник "завжди має тип System.Object, навіть якщо можливо використовувати більш конкретний тип. Параметр "е" завжди має тип System.EventArgs. Події, які не надають дані про події, повинні використовувати тип делегата System.EventHandler. Обробники подій повертають недійсні, щоб вони могли надсилати Кожна подія для кількох цільових методів. Будь-яке значення, повернене ціллю, втрачається після першого дзвінка. "

Звичайно, ми все це знаємо і все одно порушуємо правила. (Усі обробники подій можуть використовувати стандартний 'Відправник об’єкта' у своєму підписі, якщо бажано в будь-якому випадку - це безперебійна зміна.)

Таким чином, використання a SuppressMessageAttributeробить трюк:

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

Я сподіваюся, що такий підхід стане еталоном в якийсь момент майбутнього. Це дійсно дуже добре працює.

Дякую за всі ваші думки, хлопці, я дуже вдячний ...

Майк


6
Зроби це. (Не думайте, що це виправдовує відповідь.)
Конрад Рудольф,

1
Мої аргументи насправді не вказували на вас: звичайно, ви повинні робити це у власних проектах. Вони були аргументами, чому це може не працювати в БКЛ.
Томмі Карльєр,

3
Людина, я б хотів, щоб мій проект зробив це з самого початку, я ненавиджу відправку відправника.
Метт H

7
Тепер ЦЕ питання. Бачите, люди? Не одне з цих oh hi this my hom work solve it plz :code dump:питань розміром з твіттом, а питання, з якого ми дізнаємось .
Каміло Мартін

3
Ще одна пропозиція, просто назвати його EventHandler<,>чим GenericEventHandler<,>. В EventHandler<>BCL вже є загальний, який названий просто EventHandler. Тож EventHandler - більш поширене ім'я, і ​​делегати підтримують перевантаження типів
nawfal

Відповіді:


25

Здається, Microsoft підхопила це, як подібний приклад зараз на MSDN:

Загальні делегати


2
+1 Ах, чудово. Вони справді вирішили це зробити. Це добре. Я сподіваюсь, що вони зроблять це визнаним шаблоном у VS IDE, тому що, як це є зараз, більш незручно використовувати цей зразок з точки зору IntelliSense тощо.
Майк Розенблум,

13

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


1
Я впевнений, що саме в цьому причина. Однак тепер, коли новіші версії мови мають протиріччя для вирішення цього питання, схоже, вони повинні мати можливість впоратись із цим сумісно назад. Попередні обробники, які використовують 'об’єкт відправника', не зламаються. Але це не стосується старих мов і не може бути правдою для деяких сучасних мов .NET, я не впевнений.
Майк Розенблум

13

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

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

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


1
Ах, добре бачити, що є прогрес ... Цікаво, чому TResult не обмежується спадкуванням з класу "EventArgs". Базовий клас 'EventArgs' в основному порожній; можливо, вони відходять від цього обмеження?
Майк Розенблум

Це може бути нагляд з боку дизайнерської команди; хто знає.
П’єр Арно

ну, події працюють добре, не використовуючи EventArgs, це просто умовна річ
Себастьян

3
У документації TypedEventHandler конкретно зазначено, що argsбуде, nullякщо даних про події немає, тому, схоже, вони за замовчуванням відходять від використання по суті порожнього об'єкта. Я здогадуюсь, що первісна ідея полягала в тому, що метод із другим параметром типу EventArgsможе обробляти будь-яку подію, оскільки типи завжди будуть сумісні. Вони, мабуть, зараз розуміють, що вміння обробляти кілька різних подій одним методом - це не все так важливо.
jmcilhinney

1
Це не схоже на недогляд. Обмеження було видалено також із делегата System.EventHandler <TEventArgs>. referenceource.microsoft.com/#mscorlib/system/…
colton7909

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/… .
Майк Розенблум

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

Як я розумію, поле "Відправник" завжди повинно посилатися на об'єкт, який містить підписку на подію. Якби я мав свої барабани, також було б поле, що містить інформацію, достатню для скасування підписки на подію, якщо це стане необхідним (*) (розглянемо, наприклад, реєстратор змін, який підписується на події, що змінюються колекцією; він містить дві частини , один з яких виконує фактичну роботу та містить фактичні дані, а інший, який забезпечує загальнодоступну обгортку інтерфейсу, основна частина може містити слабке посилання на частину обгортки. Якщо частина обгортки збирається сміттям, це означатиме більше не було когось цікавити зібрані дані, і реєстратор змін повинен таким чином скасувати підписку на будь-яку подію, яку він отримує).

Оскільки можливо, що об’єкт може надсилати події від імені іншого об’єкта, я бачу певну корисність для того, щоб мати поле "відправник" типу "Об'єкт", а також для поля, похідного EventArgs, містити посилання на об'єкт, який повинен діяти. Однак марність поля "відправник", ймовірно, обмежена тим, що об'єкт не має чистого способу скасувати передплату від невідомого відправника.

(*) Насправді, більш чистим способом поводження з підписками було б мати тип делегата для багатоадресної передачі для функцій, які повертають булеву систему; якщо функція, викликана таким делегатом, повертає True, делегат буде виправлений, щоб видалити цей об'єкт. Це означає, що делегати більше не будуть по-справжньому незмінні, але слід мати можливість здійснити таку зміну безпечним для потоків способом (наприклад, шляхом зняття посилання на об'єкт і з використанням коду делегата багатоадресної пошти ігнорувати будь-які вбудовані нульові посилання на об'єкт). Згідно з цим сценарієм, спроба опублікувати та подію на об'єкт, що перебуває у розпорядженні, може бути оброблена дуже чисто, незалежно від місця події.


2

Озираючись на блюзнірство як єдину причину створення типу об’єкта відправника (якщо опустити проблеми з протиріччям у коді VB 2005, який є помилкою IMHO Microsoft), хто-небудь може запропонувати хоча б теоретичний мотив прибити другий аргумент до типу 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;
}

2
Так, створення окремого класу, який успадковується від System.EventArgs, може здатися неінтуїтивним і справді представляти додаткову роботу, але для цього є дуже вагома причина. Якщо вам ніколи не потрібно змінювати код, то ваш підхід прекрасний. Але реальність полягає в тому, що вам може знадобитися збільшити функціональність події в майбутній версії та додати властивості до аргументів події. У вашому сценарії вам доведеться додати додаткові перевантаження або необов'язкові параметри до підпису обробника події. Це підхід, використовуваний у VBA та застарілому VB 6.0, який є працездатним, але трохи некрасивим на практиці.
Майк Розенблум

1
Однак, успадкувавши від EventArgs, майбутня версія може успадкувати ваш старий клас аргументів події та доповнити його. Усі старі абоненти все ще можуть працювати точно так, як працює, використовуючи базовий клас вашого нового класу аргументів події. Дуже чисто. Більше роботи для вас, але більш чистого для будь-яких абонентів, які залежать від вашої бібліотеки.
Майк Розенблум

Навіть не потрібно його успадковувати, ви можете просто додати додаткову функціональність безпосередньо у свій клас аргументів подій, і він буде працювати нормально. Однак, аргументи обмеження обмеження на eventargs були зняті, оскільки це не мало особливого сенсу для багатьох сценаріїв, тобто. коли ви знаєте, вам ніколи не знадобиться розширювати функціональність певної події або коли все, що вам потрібно, - це аргумент типу значень у програмах, що відрізняються ефективністю.
Майк Мариновський,

1

У поточній ситуації (відправник є об'єктом), ви можете легко приєднати метод до кількох подій:

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

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

Якщо відправник буде загальним, ціль події клацання буде не типу кнопки або мітки, а типу управління (тому що подія визначена в контролі). Тож деякі події класу Button матимуть ціль типу Control, інші - інші цільові типи.


2
Томмі, ти можеш робити саме те саме із системою, яку я пропоную. Ви все ще можете використовувати стандартний обробник подій, у якого є параметр "відправник об'єкта" для обробки цих подій сильного типу. (Дивіться приклад коду, який я зараз додав до початкової публікації.)
Майк Розенблум,

Так, я згоден, це хороша річ у стандартних .NET подіях, прийнятих!
Лу4,

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)

все одно зможете підписатися.

EDIT

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

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


Гей, Майкл, це досить акуратна альтернатива. Мені це подобається. Як ви зазначаєте, однак, зайвим є те, щоб параметр 'відправник' був ефективно пропущений у два рази. Аналогічний підхід обговорюється тут: stackoverflow.com/questions/809609/… , і, здається, існує консенсус, що він занадто нестандартний. Ось чому я вагався, щоб запропонувати тут сильно набрану ідею відправника. (Здається, все добре сприймають, тому мені приємно.)
Майк Розенблум,

1

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

public event Action<MyEventType> EventName

де MyEventTypeне успадковує EventArgs. Навіщо турбуватися, якщо я ніколи не збираюся використовувати когось із членів EventArgs.


1
Погодьтеся! Чому ми повинні відчувати себе мавпами?
Лу4,

1
+ 1-ед. Це я теж використовую. Іноді простота виграє! Або навіть event Action<S, T>і event Action<R, S, T>т. Д. У мене є метод розширення для Raiseних безпечно для потоків :)
nawfal
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.