[英]Raising C# events with an extension method - is it bad?
我们都熟悉C#事件声明的恐怖。 为了确保线程安全, 标准是编写类似以下内容的代码 :
public event EventHandler SomethingHappened;
protected virtual void OnSomethingHappened(EventArgs e)
{
var handler = SomethingHappened;
if (handler != null)
handler(this, e);
}
最近,在该板上的另一个问题(我现在找不到)中,有人指出扩展方法可以在这种情况下很好地使用。 这是一种实现方法:
static public class EventExtensions
{
static public void RaiseEvent(this EventHandler @event, object sender, EventArgs e)
{
var handler = @event;
if (handler != null)
handler(sender, e);
}
static public void RaiseEvent<T>(this EventHandler<T> @event, object sender, T e)
where T : EventArgs
{
var handler = @event;
if (handler != null)
handler(sender, e);
}
}
使用这些扩展方法后,您需要声明和引发事件的过程是这样的:
public event EventHandler SomethingHappened;
void SomeMethod()
{
this.SomethingHappened.RaiseEvent(this, EventArgs.Empty);
}
我的问题:这是个好主意吗? 我们是否没有标准的On方法会丢失任何东西? (我注意到的一件事是,它不适用于具有显式添加/删除代码的事件。)
它仍然可以用于具有显式添加/删除操作的事件-您只需要使用委托变量(或者,您已经存储了委托)而不是事件名称。
但是,有一种更简单的方法可以使其成为线程安全的-使用无操作处理程序对其进行初始化:
public event EventHandler SomethingHappened = delegate {};
调用额外的委托对性能的影响可以忽略不计,并且可以确保使代码更容易。
顺便说一句,在扩展方法中,您不需要额外的局部变量-您可以这样做:
static public void RaiseEvent(this EventHandler @event, object sender, EventArgs e)
{
if (@event != null)
@event(sender, e);
}
static public void RaiseEvent<T>(this EventHandler<T> @event, object sender, T e)
where T : EventArgs
{
if (@event != null)
@event(sender, e);
}
就我个人而言,我不会使用关键字作为参数名称,但是它根本不会真正改变调用方,因此您可以按照自己的意愿进行:)
编辑:至于“ OnXXX”方法:您打算从中派生您的类吗? 我认为,大多数课程都应该密封。 如果这样做 ,您是否希望那些派生类能够引发该事件? 如果对这两个问题的回答均为“否”,请不要打扰。 如果两者的答案均为“是”,请执行:)
现在C#6在这里,有一种更紧凑,线程安全的方法来触发事件:
SomethingHappened?.Invoke(this, e);
由于空条件运算符“?”,只有在为事件注册了委托(即不为空)时才Invoke()
。
问题中要解决的“处理程序”代码的线程问题在这里被绕开了,因为像在该代码中一样, SomethingHappened
仅被访问一次,因此在测试和调用之间不可能将其设置为null。
这个答案也许与原始问题相切,但是对于那些寻求一种简单方法引发事件的人来说却是非常不切实际的。
[这里有个想法]
只需按照推荐的方式编写一次代码,然后完成即可。 这样您就不会混淆同事查看代码以为您做错了什么吗?
[与编写事件处理程序相比,我阅读了更多试图找到编写事件处理程序的方法的文章。]
代码更少,可读性更高。 我喜欢
如果您对性能不感兴趣,则可以这样声明事件,以避免进行空检查:
public event EventHandler SomethingHappened = delegate{};
通过将处理程序分配给局部变量并不能“确保”线程安全。 分配后,您的方法仍然可能被中断。 例如,如果在中断期间处置了用于监听事件的类,则您正在处置的类中调用方法。
您可以避免使用null引用异常,但正如Jon Skeet和cristianlibardo在其答案中指出的那样,可以采用更简单的方法来做到这一点。
另一件事是,对于非密封类,OnFoo方法应该是虚拟的,我认为扩展方法是不可能的。
为了进一步解决上述问题,您可以防止自己的处理程序中的任何一个引发异常。 如果发生这种情况,则不会调用后续处理程序。
同样,您可以对处理程序进行任务分配,以防止长时间运行的处理程序对通知后一个处理程序造成过多的延迟。 这也可以防止源线程被长时间运行的处理程序劫持。
public static class EventHandlerExtensions
{
private static readonly log4net.ILog _log = log4net.LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
public static void Taskify(this EventHandler theEvent, object sender, EventArgs args)
{
Invoke(theEvent, sender, args, true);
}
public static void Taskify<T>(this EventHandler<T> theEvent, object sender, T args)
{
Invoke(theEvent, sender, args, true);
}
public static void InvokeSafely(this EventHandler theEvent, object sender, EventArgs args)
{
Invoke(theEvent, sender, args, false);
}
public static void InvokeSafely<T>(this EventHandler<T> theEvent, object sender, T args)
{
Invoke(theEvent, sender, args, false);
}
private static void Invoke(this EventHandler theEvent, object sender, EventArgs args, bool taskify)
{
if (theEvent == null)
return;
foreach (EventHandler handler in theEvent.GetInvocationList())
{
var action = new Action(() =>
{
try
{
handler(sender, args);
}
catch (Exception ex)
{
_log.Error(ex);
}
});
if (taskify)
Task.Run(action);
else
action();
}
}
private static void Invoke<T>(this EventHandler<T> theEvent, object sender, T args, bool taskify)
{
if (theEvent == null)
return;
foreach (EventHandler<T> handler in theEvent.GetInvocationList())
{
var action = new Action(() =>
{
try
{
handler(sender, args);
}
catch (Exception ex)
{
_log.Error(ex);
}
});
if (taskify)
Task.Run(action);
else
action();
}
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.