我已经编写了一段时间,只是发现了一些有趣的东西.所以通常当我在类中实现事件时,我想在尝试调用它之前检查事件的null状态,所以我使用以下语法:
private void OnEvent(EventArgs e)
{
if (Event != null) Event(this, e);
}
Run Code Online (Sandbox Code Playgroud)
但我今天在Visual Studio中发现了一些东西,它是代码简化建议,它建议将以下语法简化为:
private void OnEvent(EventArgs e)
{
Event?.Invoke(this, e);
}
Run Code Online (Sandbox Code Playgroud)
是否有人熟悉这个"?" 句法?它是检查任何东西或只是委托的空状态的通用速记吗?它不是Linq框架的一部分,它是一种内置语法.任何洞察这个以及它的用途都会有所帮助.我没有太多的搜索,但没有具体找到任何东西.
我在上面留下了一些评论,但我觉得它们非常重要,可以被称为答案.
首先,正如其他人所说,这是空条件运算符.
此版本的代码被认为是糟糕的样式,因为检查后事件可能会变为null,如果在另一个线程上修改:
private void OnEvent(EventArgs e)
{
if (Event != null) Event(this, e);
}
Run Code Online (Sandbox Code Playgroud)
但是,我注意到在任意线程上调用,订阅和取消订阅事件的多线程程序相对较少.
添加null条件运算符之前的标准建议是写出笨拙的:
private void OnEvent(EventArgs e)
{
var ev = Event;
if (ev != null) ev(this, e);
}
Run Code Online (Sandbox Code Playgroud)
如果存在竞争,这将消除空取消引用.
null条件运算符是相同的:
private void OnEvent(EventArgs e)
{
Event?.Invoke(this, e);
}
Run Code Online (Sandbox Code Playgroud)
这只是一个简写var ev = Event; if (ev != null) ev.Invoke(this, e);.
现在,人们会告诉你,后两个版本,无论是本地的,还是更简洁的?.,都是"线程安全的". 他们不是.这些不会取消引用null,但这不是有趣的种族.有趣的比赛是:
Thread Alpha: create disposable object X -- say, a log file writer
Thread Alpha: subscribe X.H to event
Thread Bravo: Cause event to fire
Thread Bravo: Save X.H into local and check for null. It's not null.
Thread Alpha: Unsubscribe X.H from event
Thread Alpha: Call Dispose on X -- the log file is now closed.
Thread Bravo: Invoke X.H via the local
Run Code Online (Sandbox Code Playgroud)
现在我们在已处置对象上调用了一个方法,如果对象被正确实现,则应抛出"对象处置"异常.如果没有,嘿,它试图写入一个封闭的文件,一切都坏了,耶!
在多线程环境中,事件处理程序都需要是安全的调用永远,即使之后他们已经从事件退订.呼叫站点上没有任何"线程安全"可以解决该问题; 这是处理程序的要求.但是,处理程序的作者如何知道它们将在多线程环境中使用,并相应地进行规划?通常,他们没有.事情破裂了.
如果您不喜欢它,那么不要编写在多个线程上使用事件的程序.很难做到正确,每个人 - 来源和处理者 - 都必须合作以确保它有效.
现在,有人可能会说,这不是锁定处理程序的解决方案吗?
private object HandleLocker = new object();
...
private void OnEvent(EventArgs e)
{
lock (HandleLocker)
{
if (Event != null)
Event(this, e);
}
}
Run Code Online (Sandbox Code Playgroud)
然后同样添加锁定在add和remove的Event.
这是一种比疾病更糟糕的治疗方法.
Thread Bravo takes out a lock on some object Foo and obtains it.
Thread Charlie wishes to fire the event.
Thread Charlie takes out a lock on HandleLocker and obtains it.
Thread Charlie calls the handler. The handler blocks trying to obtain Foo.
Thread Bravo attempts to subscribe a new handler, and blocks on HandleLocker.
Run Code Online (Sandbox Code Playgroud)
现在我们有两个线程,每个线程都在等待另一个完成.在Charlie释放HandleLocker之前,Bravo无法移动,而且在Bravo释放Foo之前,Charlie无法移动.
永远不要这样做.同样:如果您处于多线程事件处理程序中,则需要确保在过时时调用处理程序,并且您可能不会使用锁来执行此操作.这是一个糟糕的情况; 锁是管理线程的标准机制.
多线程很难.如果您不了解事情可能出错的所有方式,以及如何避免它们,请不要编写多线程程序.