Применение MulticastDelegate
От: Venom  
Дата: 29.05.15 11:24
Оценка:
На правах майндфака.
Есть класс A, который создает экземпляр класса B и подписывается на событие этого экземпляра.
public class A {
  public void DoSmthA(){
    var b = new B();
    b.DidSmthB += () => { };
}
}
public class B {
  public void DoSmthB(){
    DidSmthB(); // += () => {};
}
  public event Action DidSmthB;
}


Как написать так, чтобы закомментированный код выполнялся после обработки DidSmthB и в контексте B (т.к. он не имеет отношения к A)?

Первая мысль была добавить колбэк как входной аргумент DidSmthB, чтобы вернуть управление в контекст B.
Но это нарушало бы целостность DidSmthB, которое кроме того что делало бы Smth, возвращало бы управление через колбек.

Второе это выполнить закомментированный код внутри, в конце обработчика DidSmthB, но при этом теряется контекст B.

Нашел решение в виде:
public class B {
  public void DoSmthB(){
    DidSmthB += () => {};
    DidSmthB();
}
  public event Action DidSmthB;
}

Это позволяет и сохранить чистоту DidSmthB и контекст B.

Не нашли бы вы такой код странным получив его на code-review?
Re: Применение MulticastDelegate
От: Sinix  
Дата: 29.05.15 11:53
Оценка: 4 (1) +3
Здравствуйте, Venom, Вы писали:

V>Нашел решение в виде:

Отписываться ещё надо И EventHandler, а не Action, если на то пошло.

Тут сама задача непонятна. Варианты:
* Надо вызывать произвольный код из A после каждого вызова DidSmthB()? — событие
* Надо вызывать произвольный код из A после _конкретного_ вызова DidSmthB()? — callback в параметре
* Класс B должен вызывать свой код после каждого вызова DidSmthB()? — ну так пусть и вызывает, что ему мешает-то?
* Класс B должен вызывать свой код после _конкретного_ вызова DidSmthB()? — флаг в параметрах.
Re[2]: Применение MulticastDelegate
От: another_coder Россия  
Дата: 09.06.15 13:23
Оценка: 4 (1) +1
Здравствуйте, Sinix, Вы писали:

S>Здравствуйте, Venom, Вы писали:


V>>Нашел решение в виде:

S>Отписываться ещё надо И EventHandler, а не Action, если на то пошло.

Оба являются наследниками MulticastDelegate. Для примера разницы нет.
Re: Применение MulticastDelegate
От: another_coder Россия  
Дата: 09.06.15 13:24
Оценка: 4 (1) +1
Здравствуйте, Venom.

Задача действительно непонятна. Вызвать метод, но в контексте созданного B из созданного A? Попробуйте описать суть задачи не слишком сильно упрощая?
Re[3]: Применение MulticastDelegate
От: Sinix  
Дата: 09.06.15 13:46
Оценка: 1 (1)
Здравствуйте, another_coder, Вы писали:

S>>И EventHandler, а не Action, если на то пошло.

_>Оба являются наследниками MulticastDelegate. Для примера разницы нет.

Это как правила гигиены: персонально оно может и ничего, но в обществе людей, которые их соблюдают, находиться гораздо приятнее

См Member Design Guidelines, Event Design.
Re[4]: Применение MulticastDelegate
От: another_coder Россия  
Дата: 09.06.15 13:51
Оценка:
Здравствуйте, Sinix, Вы писали:

S>Здравствуйте, another_coder, Вы писали:


S>>>И EventHandler, а не Action, если на то пошло.

_>>Оба являются наследниками MulticastDelegate. Для примера разницы нет.

S>Это как правила гигиены: персонально оно может и ничего, но в обществе людей, которые их соблюдают, находиться гораздо приятнее


Вы же задач не знаете, а уже советуете что лучше. Может быть ему лучше callback использовать.
И даже если нужен event, можно определить свой собственный Handler и использовать его. EventHandler в данном случае вообще не играет роли.
Re[5]: Применение MulticastDelegate
От: Sinix  
Дата: 09.06.15 14:14
Оценка:
Здравствуйте, another_coder, Вы писали:

S>>Это как правила гигиены: персонально оно может и ничего, но в обществе людей, которые их соблюдают, находиться гораздо приятнее

_>Вы же задач не знаете, а уже советуете что лучше. Может быть ему лучше callback использовать.
Тынц
Автор: Sinix
Дата: 29.05.15


_>И даже если нужен event, можно определить свой собственный Handler и использовать его. EventHandler в данном случае вообще не играет роли.

Всё уже придумано до нас. Ссылку я давал выше, цитата:

√ DO use System.EventHandler<TEventArgs> instead of manually creating new delegates to be used as event handlers.

√ CONSIDER using a subclass of EventArgs as the event argument, unless you are absolutely sure the event will never need to carry any data to the event handling method, in which case you can use the EventArgs type directly.

If you ship an API using EventArgs directly, you will never be able to add any data to be carried with the event without breaking compatibility. If you use a subclass, even if initially completely empty, you will be able to add properties to the subclass when needed.

Re[6]: Применение MulticastDelegate
От: another_coder Россия  
Дата: 09.06.15 20:01
Оценка:
Здравствуйте, Sinix.

В данном случае, на мой взгляд, это к делу не относится.
Re[7]: Применение MulticastDelegate
От: Sinix  
Дата: 09.06.15 20:22
Оценка: +1
Здравствуйте, another_coder, Вы писали:

_>В данном случае, на мой взгляд, это к делу не относится.


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

Другими словами, заблуждения надо убивать, пока они маленькие.
Re: Применение MulticastDelegate
От: agat50  
Дата: 09.06.15 20:30
Оценка: 4 (1) +1
Здравствуйте, Venom, Вы писали:

V>На правах майндфака.


В Rx такие вещи есть http://www.introtorx.com/Content/v1.0.10621.0/15_SchedulingAndThreading.html#SubscribeOnObserveOn Хотя мне тоже не очень понятна задача, синхронный код даст один и тот же контекст, мб http://www.introtorx.com/Content/v1.0.10621.0/19_DispellingMyths.html#DispellingEventMyths тоже стоит пролистать.
Re[8]: Применение MulticastDelegate
От: Venom  
Дата: 10.06.15 03:11
Оценка:
Здравствуйте, Sinix, Вы писали:

Продолжу ветку another_coder и то, о чем сам хотел написать в числе прочего.
С самого начала:
> EventHandler, а не Action, если на то пошло.
Action не тащит за собой EventArgs, а для передачи данных через EventArgs, требуется наследоваться от него в виде EventArgsSmth, куда уже и ложить данные.
Что приводит к необходимости использовать костыль в виде EventArgs<T>: http://stackoverflow.com/questions/3312134/does-net-have-a-built-in-eventargst
Поэтому, проще использовать Action.
А если обернуть его в event, который "добавит инкапсюляции", не позволяя внешним классам переопределять наш Action, то становится совсем как в обычном ивенте.
Собственно, насколько я понимаю, отличия от стандартного определения через EventHandler пропадают.
Да, не совсем стандартно, зато экономит кучу кода (если часто использовать EventArgs<T>).
И дизайн гайдлайны Цвалины писались до .Net 3.5 (если это его гайды, стиль похож ).


ЗЫ. Если есть мысли по поводу отличий реализаций события через EventHandler от Action, пишите. Вдруг я что-то упустил.
Отредактировано 10.06.2015 3:26 Venom . Предыдущая версия .
Re[2]: Применение MulticastDelegate
От: Venom  
Дата: 10.06.15 03:24
Оценка:
Здравствуйте, agat50, Вы писали:

A>В Rx такие вещи есть http://www.introtorx.com/Content/v1.0.10621.0/15_SchedulingAndThreading.html#SubscribeOnObserveOn Хотя мне тоже не очень понятна задача, синхронный код даст один и тот же контекст, мб http://www.introtorx.com/Content/v1.0.10621.0/19_DispellingMyths.html#DispellingEventMyths тоже стоит пролистать.


Да, ключевой момент в том, что код асинхронен, о чём я не написал.

Касаемо приведенного мною в стартовом посте кода, то он неверен.
Вот его linqpad вариант (поправленный, но всё ещё концептуально неверный):
public class A {
  public void DoSmthA() {
    Console.WriteLine("1");  
    var b = new B();
    b.DidSmthB += () => {
      Console.WriteLine("2"); 
    };
    b.DoSmthB();
  }
}
public class B {
  public void DoSmthB() {
    DidSmthB += () => {
      Console.WriteLine("3"); 
    };
    DidSmthB(); 
  }
  public event Action DidSmthB;
}

// usage
void Main()
{
  var a = new A();
  a.DoSmthA();
}


Так вот, в синхронном варианте получается всё нормально: вывод 3 после вывода 2.
А в асинхронном варианте (возьмём терминологию request/response для иллюстрации) request 3 уходит после request 2, но в каком порядке вернутся response 2 и 3, в общем случае, неизвестно.
Насколько я понимаю, можно еще использовать чтобы нам было без разницы в каком порядке вернутся 2 и 3, и мы эмулируем тот факт что 2 должно вернуться до 3. (кстати, если нарисовать таймлайн запросов, то по быстродействию, пожалуй, этот вариант будет наилучшим)
Получается, что гарантировать возврат 3 после возврата 2 можно либо callback'ом, либо каким-нибудь ManualResetEvent.

ЗЫ. За упоминание Rx плюс
Re[2]: Применение MulticastDelegate
От: Venom  
Дата: 10.06.15 03:35
Оценка:
Здравствуйте, another_coder, Вы писали:

_>Задача действительно непонятна. Вызвать метод, но в контексте созданного B из созданного A? Попробуйте описать суть задачи не слишком сильно упрощая?


Да, я неправильно описал. Спасибо за пинок, кстати, а то бы я так еще дольше отвечал.
Вот здесь описал задачу и написал почему предложенный подход не подойдет в случае асинхронности: http://rsdn.ru/forum/dotnet/6073681.1
Автор: Venom
Дата: 10.06.15

Насчет callback ты прав, кстати, именно он тут и нужен (там кстати я еще про ManualResetEvent написал, но не пробовал еще такого. При необходимости сократить таймлайн выполениния асинхронных запросов может быть хорошим вариантом).
Re[3]: Применение MulticastDelegate
От: Sinix  
Дата: 10.06.15 05:47
Оценка: 67 (2) +2
Здравствуйте, Venom, Вы писали:

V>Здравствуйте, agat50, Вы писали:


A>>В Rx такие вещи есть http://www.introtorx.com/Content/v1.0.10621.0/15_SchedulingAndThreading.html#SubscribeOnObserveOn Хотя мне тоже не очень понятна задача, синхронный код даст один и тот же контекст, мб http://www.introtorx.com/Content/v1.0.10621.0/19_DispellingMyths.html#DispellingEventMyths тоже стоит пролистать.


V>Да, ключевой момент в том, что код асинхронен, о чём я не написал.

Как всегда, "принеси то, не знаю что"

Непонятно, как сделать API — начните со сценариев использования. Представьте, что сам код уже написан и попробуйте набросать _реальный_ (это важно) сценарий использования с помощью этого кода. Сами увидите все узкие места.
Если сценария использования нет — вам не нужен этот код. Выбросите и забудьте до момента, пока не понадобится.

Если сценарий есть — пишите его сразу, иначе ничего полезного вам никто не посоветует.
В вашем случае проблема в том, что вы пытаетесь соорудить на event обработку цепочки асинхронных операций, что в принципе неверно. Хотите достать ваших пользователей — используйте Rx. Не хотите — используйте Task.

Начните с очевидного
await new B().DoSmthBAsync()


дальше будет или
using (await new B().BeginDoSmthBAsync())
{
  Console.WriteLine("2");
}


или, если нужно асинхронное завершение —
await new B()
  .DoSmthBAsync(
    async () => Console.WriteLine("2")); // Func<Task>


Есть желание извратиться по максимуму — добавьте в EventArgs свойство ResultTask с сеттером и дожидайтесь в DoSmthB() завершения задач.
Пользователи одобрят

P.S. Порядок вызова подписчиков событий в общем случае не определён. Особенно для асинхронного кода. Нужны зависимости — декларируйте это в API явно.
Re[3]: Применение MulticastDelegate
От: Sinix  
Дата: 10.06.15 06:10
Оценка: 1 (1)
Здравствуйте, Venom, Вы писали:

V>там кстати я еще про ManualResetEvent написал, но не пробовал еще такого. При необходимости сократить таймлайн выполениния асинхронных запросов может быть хорошим вариантом

Не будет, ибо context switch.

Попробуйте соорудить на MRE простенький task swarm типа такого
Автор: Sinix
Дата: 13.02.14
. Уже обсуждалось где-то тут
Автор: Cynic
Дата: 14.03.15
.
Re[9]: Применение MulticastDelegate
От: Sinix  
Дата: 10.06.15 11:17
Оценка: 1 (1)
Здравствуйте, Venom, Вы писали:

V>Поэтому, проще использовать Action.

Нет, тут другая логика: если делаем event — делаем сразу правильную реализацию, с EventHandler, protected virtual OnSomeEvent(EventArgs e) etc.

Во-первых, оно так и будет в реальном коде, если вы хоть немного следите за его качеством.
Во-вторых это покажет реальную стоимость варианта с событием, что должно вас ещё раз подтолкнуть к выводу, что событие здесь не совсем подходит (в предыдущем посте уже написал про таски).


V>А если обернуть его в event, который "добавит инкапсюляции", не позволяя внешним классам переопределять наш Action, то становится совсем как в обычном ивенте.

А зачем на ровном месте изобретать точно такой же эвент, но другой? Ладно был бы понятный сценарий, как в wpf с attached routing event, но тут-то зачем?


>Что приводит к необходимости использовать костыль в виде EventArgs<T>: ìhttp://stackoverflow.com/questions/3312134/does-net-have-a-built-in-eventargst


А почитайте ответ по своей ссылке, там всё написано. С своей стороны могу сказать следующее:

И утром ото сна восстав, читай усиленно устав(с)!

√ CONSIDER using a subclass of EventArgs as the event argument, unless you are absolutely sure the event will never need to carry any data to the event handling method, in which case you can use the EventArgs type directly.

If you ship an API using EventArgs directly, you will never be able to add any data to be carried with the event without breaking compatibility. If you use a subclass, even if initially completely empty, you will be able to add properties to the subclass when needed.

И смотрим на решение с EventArgs<T>. С интересом посмотрю, как вы будете добавлять в него поля без ломающих изменений

Рекомендации не из пальца высосаны, за каждым — длинный хвост разнообразных фейлов и "жаль, что мы не знали этого раньше".
Одно дело топтать тропинку среди нехоженных граблей — оно как бы почётно, безумству храбрых и всё такое.
Но упорно откапывать грабли там, где давно проложено шоссе — вот этого я решительно не понимаю



V>И дизайн гайдлайны Цвалины писались до .Net 3.5 (если это его гайды, стиль похож ).

Ну и что там такого нового, что кардинально меняет правила?

Вторая редакция обновлена
Автор: SergeyT.
Дата: 29.06.09
до более-менее актуального состояния. Кроме того периодически всплывают дополнения, например, для тасков и await.


V>ЗЫ. Если есть мысли по поводу отличий реализаций события через EventHandler от Action, пишите. Вдруг я что-то упустил.

Чисто технически — никаких, это вопрос гигиены кода и культуры кодирования.

Таких соглашений много, "Соблюдайте стиль именования", "Не используйте публичные поля", "Не падайте с NullRefException", "Объявляйте события правильно" и т.д. и т.п.
Под каждым пунктом есть своё обоснование и границы применимости, но это ж надо читать те самые гадлайны, которые "да какая разница?".
Re[4]: Применение MulticastDelegate
От: Venom  
Дата: 11.06.15 04:27
Оценка: 38 (1)
Здравствуйте, Sinix, Вы писали:

S>Как всегда, "принеси то, не знаю что"


S>Непонятно, как сделать API — начните со сценариев использования. Представьте, что сам код уже написан и попробуйте набросать _реальный_ (это важно) сценарий использования с помощью этого кода. Сами увидите все узкие места.

S>Если сценария использования нет — вам не нужен этот код. Выбросите и забудьте до момента, пока не понадобится.

S>Если сценарий есть — пишите его сразу, иначе ничего полезного вам никто не посоветует.

S>В вашем случае проблема в том, что вы пытаетесь соорудить на event обработку цепочки асинхронных операций, что в принципе неверно. Хотите достать ваших пользователей — используйте Rx. Не хотите — используйте Task.

Во-первых, спасибо за ценные замечания.

>Непонятно, как сделать API — начните со сценариев использования.

>Непонятно, как сделать API — начните со сценариев использования. Представьте, что сам код уже написан и попробуйте набросать _реальный_ (это важно) сценарий использования с помощью этого кода. Сами увидите все узкие места.
>Если сценария использования нет — вам не нужен этот код. Выбросите и забудьте до момента, пока не понадобится.

Всё верно. Нет, я не высосал этот пример из пальца.
Сценарий использования:
Есть вью А, нажата кнопка в этом вью, вызвано модальное окно Б (пусть будет модальное окно), в него переданы данные из А;
Далее в Б нажимается кнопка, формируется асинхр. запрос "изменение данных" (на изменение одного объекта данных), в него добавляются изменения, сервер возвращает ответ;
(сервер мог бы и сразу отдавать объект в ответе, кстати)
Далее с сервера запрашивается этот объект данных, парсится, находится в текущем наборе данных по айди, заменяется, UI обновляется (INPC).
(объект повторно запрашивается с сервера для "надежности", т.е. сервер может сохранить объект не совсем так как его видит клиент)

Вообще, мне изначально нужно было заколбэчиться из Б обратно в А (из-за чего и задал этот вопрос), но потом я как-то решил эту проблему на уровне кода и необходимость в этом отпала.

>В вашем случае проблема в том, что вы пытаетесь соорудить на event обработку цепочки асинхронных операций, что в принципе неверно. Хотите достать ваших пользователей — используйте Rx. Не хотите — используйте Task.

Мне таски недоступны, к сожалению (.Net < 4.0), поэтому, т.к. у меня 1 подписчик — 1 издатель (в терминах событий) я использую колбэки.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.