Создавая событие в компонентах и control-ах, старайтесь описывать их по следующей схеме. Определите два параметра с именами sender и e. Параметр sender описывает объект, инициировавший событие, и всегда должен быть типа object, даже если возможно использование более конкретного типа. Второй параметр, e, должен содержать состояние и дополнительную информацию, соответствующую событию. Этот параметр должен быть конкретного типа, относящегося к событию.
Собственно, а почему sender должен быть именно только object? Или это касается только контролов и компонент, а произвольных классов не касается?
Здравствуйте, naf2000, Вы писали:
N>вот здесь прочел следующее: N>
N>Создавая событие в компонентах и control-ах, старайтесь описывать их по следующей схеме. Определите два параметра с именами sender и e. Параметр sender описывает объект, инициировавший событие, и всегда должен быть типа object, даже если возможно использование более конкретного типа. Второй параметр, e, должен содержать состояние и дополнительную информацию, соответствующую событию. Этот параметр должен быть конкретного типа, относящегося к событию.
N>Собственно, а почему sender должен быть именно только object? Или это касается только контролов и компонент, а произвольных классов не касается?
Для унификации. Если хочется иметь в обработчике типизированный sender, можно передавать его через EventArgs.
Help will always be given at Hogwarts to those who ask for it.
Здравствуйте, naf2000, Вы писали:
N>почему нельзя было проще реализовать: N>
MouseMove(this, e);
N>если никто не подписан, то и никто не вызван
Это очень правильный вопрос!
N>и еще, если event такой умный, что его вызов есть последовательный вызов всех соответствующих методов его подписчиков, то на кой надо писать так: N>
Здравствуйте, naf2000, Вы писали:
N>и еще, если event такой умный, что его вызов есть последовательный вызов всех соответствующих методов его подписчиков, то на кой надо писать так:
N>if (MouseMove != null)
N> MouseMove(this, e);
N>почему нельзя было проще реализовать:
N>MouseMove(this, e);
N>если никто не подписан, то и никто не вызван
Потому что это пришлось бы поддерживать не для event-ов, а для любых переменных типа Delegate. И получилось бы не очень стройно по сравнению с использованием всех остальных переменных. Если это сильно напрягает, можно написать хелпер
Help will always be given at Hogwarts to those who ask for it.
Здравствуйте, Qbit86, Вы писали:
Q>Так что лучше по возможности использовать простой и понятный IObservable<T> из Rx вместо event'ов.
RX и эвенты решают слегка разные задачи, и пропихивать везде RX (этож новая фича!) не менее глупо, чем пропихивать везде эвенты (они ж всегда были!).
Здравствуйте, _FRED_, Вы писали:
_FR>Потому что это пришлось бы поддерживать не для event-ов, а для любых переменных типа Delegate.
То есть не по объективным причинам, а по причинам совместимости с сомнительными архитектурными решениями при разработке языка. События используются в сценариях, отличных от общих сценариев использования делегатов. Но тем не менее создатели языка решили их скрестить, пожертвовав чистотой и удобством использования.
Здравствуйте, Qbit86, Вы писали:
_FR>>Потому что это пришлось бы поддерживать не для event-ов, а для любых переменных типа Delegate.
Q>То есть не по объективным причинам, а по причинам совместимости с сомнительными архитектурными решениями при разработке языка.
Для вас так важно назвать данное решение "сомнительным"?
Q>События используются в сценариях, отличных от общих сценариев использования делегатов. Но тем не менее создатели языка решили их скрестить, пожертвовав чистотой и удобством использования.
Я с таким мнением "создателей языка" пока не знаком
Help will always be given at Hogwarts to those who ask for it.
Здравствуйте, Qbit86, Вы писали:
S>>RX и эвенты решают слегка разные задачи, Q>GoF-паттерн Observer.
Поясните плиз, что вы хотели сказать. С учётом этого
Здравствуйте, _FRED_, Вы писали:
_FR>Для вас так важно назвать данное решение "сомнительным"?
Я просто констатировал факт, что дёргание ивентов — тёмный уголок в C#, причём это оправдывается соображениями типа «так вышло».
Чего я хочу как автор сендера: чтобы уведомление подписчиков о новом событии, ошибке или окончании потока событий выполнялось просто и прозрачно, без приседаний с сохранением временных ссылок, проверками их на null, etc.
Чего я хочу как автор подписчика: чтобы при подписке мне возвращался токен, с помощью которого я могу отписаться в RAII-стиле, без указания своих обработчиков справа от «-=». Вообще замечательно, если с потоком событий я могу работать как с push-коллекцией в привычном LINQ-подходе.
Стандартный синтаксис event'ов этим требованиям не удовлетворяет. Всякие weak event pattern'ы и прочие богомерзкие извращения использовать желания нет.
Здравствуйте, Sinix, Вы писали:
S>>>RX и эвенты решают слегка разные задачи, Q>>GoF-паттерн Observer. S>Поясните плиз, что вы хотели сказать. С учётом этого
Здравствуйте, Qbit86, Вы писали:
S>>>>RX и эвенты решают слегка разные задачи, Q>>>GoF-паттерн Observer. S>>Поясните плиз, что вы хотели сказать. С учётом этого
Q>В «этом» обсуждался Visitor, а не Observer.
Имел в виду исключительно первый пост. Конкретно — часть "об излишней любви к паттернам ради паттернов".
По-прежнему интересует: как ваше утверждение "и эвенты и IObservable можно обозвать реализацией паттерна обозреватель" соотносится с моим "RX и эвенты решают слегка разные задачи"
Здравствуйте, Sinix, Вы писали:
S>Имел в виду исключительно первый пост. Конкретно — часть "об излишней любви к паттернам ради паттернов".
О «паттернах ради паттернов» речь не идёт, а тем более о любви к ним.
Не нравится «Observer/Observable», называй их «Receiver/Sender», «Listener/Notifyer», «Subscriber/Publisher», you name it.
S>По-прежнему интересует: как ваше утверждение "и эвенты и IObservable можно обозвать реализацией паттерна обозреватель" соотносится с моим "RX и эвенты решают слегка разные задачи" :???:
Здравствуйте, Qbit86, Вы писали:
Q>Ну, я не считаю, что они решают разные задачи.
Во-первых:
RX хорош для ленивой обработки последовательностей, особенно с LINQ.
Эвенты — для непосредственного реагирования на события аля
Ну это совсем простой пример. В случае, когда источник событий живёт дольше подписчиков, нужно заморачиваться с потенциальными утечками наблюдателей, хранением ссылки на обработчик с целью отписки, etc.
S>Для подобных задач RX будет оверкиллом.
Да, в условиях использования большого количества библиотек и фреймворков (особенно GUI-), завязанных на классические события, притягивать Rx-прослойку за уши к каждому событию нецелесообразно. Но это не из-за того, что у них цели разные.
Здравствуйте, Qbit86, Вы писали: _FR>>Для вас так важно назвать данное решение "сомнительным"? Q>Я просто констатировал факт, что дёргание ивентов — тёмный уголок в C#, причём это оправдывается соображениями типа «так вышло».
"Дёргание" вовсе не тёмный и в никаких оправданиях не нуждается. Q>Чего я хочу как автор сендера: чтобы уведомление подписчиков о новом событии, ошибке или окончании потока событий выполнялось просто и прозрачно, без приседаний с сохранением временных ссылок, проверками их на null, etc.
Это делается очень просто:
для EventHandler<>
[SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "Event")]
[SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling")]
public static partial class Event
{
#region Methods
[SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate", Justification = Justifications.HelperForEvent)]
public static bool Raise<TEventArgs>(this EventHandler<TEventArgs> handler, object sender, TEventArgs args) where TEventArgs : EventArgs {
if(handler != null) {
handler(sender, args);
return true;
}//ifreturn false;
}
[SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate", Justification = Justifications.HelperForEvent)]
[SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "2", Justification = Justifications.ParameterIsValidated)]
public static bool Raise<TEventArgs>(this EventHandler<TEventArgs> handler, object sender, Func<TEventArgs> args) where TEventArgs : EventArgs {
Argument.NotNull(args, "args");
if(handler != null) {
var e = args();
handler(sender, e);
return true;
}//ifreturn false;
}
#endregion Methods
}
для остальных делегатов в mscorlib и System
<#@ template language="C#" hostspecific="True" #>
<#@ output extension=".g.cs" #>
<#@ assembly name="System.Core.dll" #>
<#@ import namespace="System" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.ComponentModel" #>
<#@ import namespace="System.Diagnostics" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Reflection" #>
// ----------------------------------------------------------------------------
// <autogenerated>
// This file was generated using <#= System.IO.Path.GetFileName(Host.TemplateFile) #>.
// Any changes made manually will be lost next time the file is regenerated.
// </autogenerated>
// ----------------------------------------------------------------------------using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
namespace GExtensions
{
partial class Event
{
#region Methods
<# RaiseStandardHandlers(typeof(int).Assembly); /* mscorlib */ #>
<# RaiseStandardHandlers(typeof(TypeDescriptor).Assembly); /* System */ #>
#endregion Methods
}
}
<#+
private void RaiseStandardHandlers(Assembly assembly) {
if(assembly == null) {
throw new ArgumentNullException("assembly");
}//ifvar types = from type in assembly.GetExportedTypes()
where typeof(Delegate).IsAssignableFrom(type)
where !type.IsAbstract
where !type.IsGenericTypeDefinition
let invoke = type.GetMethod("Invoke", BindingFlags.Public | BindingFlags.Instance)
where invoke != null
where invoke.ReturnType == typeof(void)
let parameters = invoke.GetParameters()
where parameters != null && parameters.Length == 2
where Array.TrueForAll(parameters, p => !p.IsOut && !p.IsLcid && !p.IsRetval)
where parameters[0].ParameterType == typeof(object)
let args = parameters[1].ParameterType
where typeof(EventArgs).IsAssignableFrom(args)
select Tuple.Create(type, args);
foreach(var type in types) {
Raise(type.Item1.FullName, type.Item2.FullName);
}//for
}
private void Raise(string handler, string args) {
#>
#region <#= handler #>
[SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate", Justification = Justifications.HelperForEvent)]
public static bool Raise(this <#= handler #> handler, object sender, <#= args #> args) {
if(handler != null) {
handler(sender, args);
return true;
}//ifreturn false;
}
[SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate", Justification = Justifications.HelperForEvent)]
[SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "2", Justification = Justifications.ParameterIsValidated)]
public static bool Raise(this <#= handler #> handler, object sender, Func<<#= args #>> args) {
Argument.NotNull(args, "args");
if(handler != null) {
var e = args();
handler(sender, e);
return true;
}//ifreturn false;
}
#endregion <#= handler #>
<#+
}
#>
У каждого (у многих) делегата появляется метод Raise который решает и проблему проверки на null и даже потокобезопастность разруливает. Q>Чего я хочу как автор подписчика: чтобы при подписке мне возвращался токен, с помощью которого я могу отписаться в RAII-стиле, без указания своих обработчиков справа от «-=». Вообще замечательно, если с потоком событий я могу работать как с push-коллекцией в привычном LINQ-подходе. Q>Стандартный синтаксис event'ов этим требованиям не удовлетворяет. Всякие weak event pattern'ы и прочие богомерзкие извращения использовать желания нет.
Чего в ваших выкладках не хватает — так это ширины взгляда. Как в такой моделе создавать подписчики из дизайнера?
Help will always be given at Hogwarts to those who ask for it.