На правах майндфака.
Есть класс 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?
Здравствуйте, Venom, Вы писали:
V>Нашел решение в виде:
Отписываться ещё надо И EventHandler, а не Action, если на то пошло.
Тут сама задача непонятна. Варианты:
* Надо вызывать произвольный код из A после каждого вызова DidSmthB()? — событие
* Надо вызывать произвольный код из A после _конкретного_ вызова DidSmthB()? — callback в параметре
* Класс B должен вызывать свой код после каждого вызова DidSmthB()? — ну так пусть и вызывает, что ему мешает-то?
* Класс B должен вызывать свой код после _конкретного_ вызова DidSmthB()? — флаг в параметрах.
Здравствуйте, Sinix, Вы писали:
S>Здравствуйте, Venom, Вы писали:
V>>Нашел решение в виде: S>Отписываться ещё надо И EventHandler, а не Action, если на то пошло.
Оба являются наследниками MulticastDelegate. Для примера разницы нет.
Здравствуйте, another_coder, Вы писали:
S>>И EventHandler, а не Action, если на то пошло. _>Оба являются наследниками MulticastDelegate. Для примера разницы нет.
Это как правила гигиены: персонально оно может и ничего, но в обществе людей, которые их соблюдают, находиться гораздо приятнее
Здравствуйте, Sinix, Вы писали:
S>Здравствуйте, another_coder, Вы писали:
S>>>И EventHandler, а не Action, если на то пошло. _>>Оба являются наследниками MulticastDelegate. Для примера разницы нет.
S>Это как правила гигиены: персонально оно может и ничего, но в обществе людей, которые их соблюдают, находиться гораздо приятнее
Вы же задач не знаете, а уже советуете что лучше. Может быть ему лучше callback использовать.
И даже если нужен event, можно определить свой собственный Handler и использовать его. EventHandler в данном случае вообще не играет роли.
Здравствуйте, another_coder, Вы писали:
S>>Это как правила гигиены: персонально оно может и ничего, но в обществе людей, которые их соблюдают, находиться гораздо приятнее _>Вы же задач не знаете, а уже советуете что лучше. Может быть ему лучше callback использовать. Тынц
_>И даже если нужен 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.
Здравствуйте, another_coder, Вы писали:
_>В данном случае, на мой взгляд, это к делу не относится.
Относится. Это часть культуры кодирования, точно так же как "не оставлять неотформатированный код" или "не использовать без необходимости изменяемые структуры". Такие вещи должны быть отработаны до уровня рефлексов, чтобы не тратить на них время и занимать мозг более полезными вещами.
Другими словами, заблуждения надо убивать, пока они маленькие.
Продолжу ветку 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, пишите. Вдруг я что-то упустил.
Да, ключевой момент в том, что код асинхронен, о чём я не написал.
Касаемо приведенного мною в стартовом посте кода, то он неверен.
Вот его 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;
}
// usagevoid 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.
Здравствуйте, another_coder, Вы писали:
_>Задача действительно непонятна. Вызвать метод, но в контексте созданного B из созданного A? Попробуйте описать суть задачи не слишком сильно упрощая?
Да, я неправильно описал. Спасибо за пинок, кстати, а то бы я так еще дольше отвечал.
Вот здесь описал задачу и написал почему предложенный подход не подойдет в случае асинхронности: http://rsdn.ru/forum/dotnet/6073681.1
Насчет callback ты прав, кстати, именно он тут и нужен (там кстати я еще про ManualResetEvent написал, но не пробовал еще такого. При необходимости сократить таймлайн выполениния асинхронных запросов может быть хорошим вариантом).
Непонятно, как сделать 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 явно.
Здравствуйте, Venom, Вы писали:
V>там кстати я еще про ManualResetEvent написал, но не пробовал еще такого. При необходимости сократить таймлайн выполениния асинхронных запросов может быть хорошим вариантом
Не будет, ибо context switch.
Попробуйте соорудить на MRE простенький task swarm типа такого
Здравствуйте, 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 (если это его гайды, стиль похож ).
Ну и что там такого нового, что кардинально меняет правила?
до более-менее актуального состояния. Кроме того периодически всплывают дополнения, например, для тасков и await.
V>ЗЫ. Если есть мысли по поводу отличий реализаций события через EventHandler от Action, пишите. Вдруг я что-то упустил.
Чисто технически — никаких, это вопрос гигиены кода и культуры кодирования.
Таких соглашений много, "Соблюдайте стиль именования", "Не используйте публичные поля", "Не падайте с NullRefException", "Объявляйте события правильно" и т.д. и т.п.
Под каждым пунктом есть своё обоснование и границы применимости, но это ж надо читать те самые гадлайны, которые "да какая разница?".
Здравствуйте, Sinix, Вы писали:
S>Как всегда, "принеси то, не знаю что"
S>Непонятно, как сделать API — начните со сценариев использования. Представьте, что сам код уже написан и попробуйте набросать _реальный_ (это важно) сценарий использования с помощью этого кода. Сами увидите все узкие места. S>Если сценария использования нет — вам не нужен этот код. Выбросите и забудьте до момента, пока не понадобится.
S>Если сценарий есть — пишите его сразу, иначе ничего полезного вам никто не посоветует. S>В вашем случае проблема в том, что вы пытаетесь соорудить на event обработку цепочки асинхронных операций, что в принципе неверно. Хотите достать ваших пользователей — используйте Rx. Не хотите — используйте Task.
Во-первых, спасибо за ценные замечания.
>Непонятно, как сделать API — начните со сценариев использования. >Непонятно, как сделать API — начните со сценариев использования. Представьте, что сам код уже написан и попробуйте набросать _реальный_ (это важно) сценарий использования с помощью этого кода. Сами увидите все узкие места. >Если сценария использования нет — вам не нужен этот код. Выбросите и забудьте до момента, пока не понадобится.
Всё верно. Нет, я не высосал этот пример из пальца.
Сценарий использования:
Есть вью А, нажата кнопка в этом вью, вызвано модальное окно Б (пусть будет модальное окно), в него переданы данные из А;
Далее в Б нажимается кнопка, формируется асинхр. запрос "изменение данных" (на изменение одного объекта данных), в него добавляются изменения, сервер возвращает ответ;
(сервер мог бы и сразу отдавать объект в ответе, кстати)
Далее с сервера запрашивается этот объект данных, парсится, находится в текущем наборе данных по айди, заменяется, UI обновляется (INPC).
(объект повторно запрашивается с сервера для "надежности", т.е. сервер может сохранить объект не совсем так как его видит клиент)
Вообще, мне изначально нужно было заколбэчиться из Б обратно в А (из-за чего и задал этот вопрос), но потом я как-то решил эту проблему на уровне кода и необходимость в этом отпала.
>В вашем случае проблема в том, что вы пытаетесь соорудить на event обработку цепочки асинхронных операций, что в принципе неверно. Хотите достать ваших пользователей — используйте Rx. Не хотите — используйте Task.
Мне таски недоступны, к сожалению (.Net < 4.0), поэтому, т.к. у меня 1 подписчик — 1 издатель (в терминах событий) я использую колбэки.