C# 自测试委托:在调用前避免检查null?
在以一般方式调用事件之前,是否有任何明智的方法来避免对事件进行空性测试的冗长?很明显,如果我呼叫一名代表,我希望将其分配。C# 自测试委托:在调用前避免检查null?,c#,.net,C#,.net,在以一般方式调用事件之前,是否有任何明智的方法来避免对事件进行空性测试的冗长?很明显,如果我呼叫一名代表,我希望将其分配。 (如果我真的想/需要测试它的空值,我最终可以显式地进行测试,但是系统地进行这个测试有点乏味和冗长。) public委托void ResetTradesDelegate(); 公共重置交易授权重置交易; public void OnSessionRxAdmMessage(IVfxFixAppSession会话,FixMessage msg) { if(ResetTra
(如果我真的想/需要测试它的空值,我最终可以显式地进行测试,但是系统地进行这个测试有点乏味和冗长。)
public委托void ResetTradesDelegate();
公共重置交易授权重置交易;
public void OnSessionRxAdmMessage(IVfxFixAppSession会话,FixMessage msg)
{
if(ResetTradesEvent!=null)//
以这种方式声明一个事件意味着它永远不会为空。它至少会连接一个无操作事件处理程序
对你来说,可能
public event ResetTradesDelegate ResetTradesEvents = delegate{};
触发一个事件总是会有一个与之相关联的竞态条件。如果代理为null,您可能会尝试调用代理,或者在事件解除挂钩后调用代理。Eric Lippert就此主题写了一篇相当全面的文章。上述技术仍然受到第二种竞态条件的影响,因此事件处理程序需要健壮,能够在事件解除挂钩后被调用
static void CallIfNotNull(this Action action)
{
if (action != null) action();
}
作为一种扩展方法,使用起来非常方便。您可以使用始终订阅的无操作事件创建事件处理程序:
public class MyClass
{
public MyClass()
{
this.Event += (sender, e) => ();
}
public event EventHandler Event;
protected virtual void OnEvent()
{
this.Event(this, EventArgs.Empty);
}
}
但是,这需要订阅您的事件,并且会导致性能损失,因为在订阅的事件处理程序列表中仍然存在no-op委托
我在这里的首选方法是创建一对扩展方法来调用任何事件处理程序,同时执行空安全检查:
public static void Raise(this EventHandler @event, object sender)
{
if(@event != null)
{
@event.Invoke(sender, EventArgs.Empty);
}
}
public static void Raise<TEventArgs>(
this EventHandler<TEventArgs> @event,
object sender,
TEventArgs args)
where TEventArgs : EventArgs
{
if(@event != null)
{
@event.Invoke(sender, args);
}
}
这纯粹是语法上的甜点;您仍在对委托进行检查。然而,这是一种很好的可重复使用的方式来结束C语言中这一难闻的部分。使用null条件运算符可以保持测试,但对我来说不太冗长。我不认为它解决了其他人提到的竞争条件
ResetTradesEvent?.Invoke();
这是在Visual Studio 2017中自动建议的。我总是在测试之前复制它(以防订阅在检查和调用之间发生变化),但我的理解是,测试是必要的。有些人采取的一种方法是在对象的构造函数中为事件添加一个无操作处理程序。它不是特别优雅,但它确实避免了空检查的需要。我认为这不是委托的问题,而是事件的问题(哪个使用MulticastDelegate?)以及它们是如何实现的。在这里,如果您将一个“虚拟”委托对象(分配给非事件成员),则不需要进行任何检查,它与任何其他可以为null的值没有什么不同。我同意@dlev,但我认为它“更好”要检查某些内容是否不为null,在使用itI之前,我会说只需使用前面提到的no-op处理程序dlev。无论您如何编写检查,它都不会100%安全。这取决于扩展方法的语义。如果此操作操作
复制了对操作
的引用值,则是的。Otherwise,如果操作被另一个线程修改,您仍然可以得到NullReferenceException
。@MikeBantegui,参数的行为类似于局部变量。它们是副本。这实际上很有趣。因此,在扩展方法中修改结构的值并不能达到预期的效果。+1不幸的是,扩展该方法很繁琐s over Func和不同的签名..另外,对于事件
如果存在多线程,我认为一般的建议是在访问支持的MulticastDelegate时获取锁..不确定隐式转换到操作如何影响此操作。@pst,此模式正是在并发访问时应该做的。parameter是事件值的线程安全快照。此代码是线程安全的。由于委托是不可变的,因此不需要锁。在添加事件然后删除它之前(因此有0个有线事件)。现在事件委托再次为null
。因为我以前受此问题的影响(anull
在预期不为null的情况下),我建议不要使用这种方法。.我刚刚编写了一个测试程序,它使用public event handler ValueChanged=delegate{};
订阅事件obj.ValueChanged+=new EventHandler(obj_ValueChanged);
,更改一个值(从而触发事件处理程序),取消订阅obj.ValueChanged-=obj_ValueChanged;
,再次更改值(不触发事件),但不发生任何空引用异常。添加事件意味着将有2个事件处理程序连接。删除该事件将使您返回1(无操作委托)。是否100%确定没有执行其他操作?不仅会调用无操作处理程序,而且事件现在也将始终引用具体化的委托对象(vs.null
).我相信使用null
至少在概念上是一个设计决策,目的是最大限度地减少未连接事件不需要的对象。无论如何,仍然是+1.而且我认为只需重命名event
然后使用关键字分隔符语法会更干净。“关键字分隔符语法”;我喜欢这个短语。你有更合适的参数名称吗?在将它添加到我自己的库中时,我对此考虑了一些,但找不到任何我个人认为更清楚的。请添加一些解释
public static void Raise(this EventHandler @event, object sender)
{
if(@event != null)
{
@event.Invoke(sender, EventArgs.Empty);
}
}
public static void Raise<TEventArgs>(
this EventHandler<TEventArgs> @event,
object sender,
TEventArgs args)
where TEventArgs : EventArgs
{
if(@event != null)
{
@event.Invoke(sender, args);
}
}
this.ResetTradesEvent.Raise(this);
public static void Call(this Action action)
{
var safeAction = Interlocked.CompareExchange(ref action, null, null);
if (safeAction != null)
safeAction();
}
ResetTradesEvent?.Invoke();