快速C#语法说明

Wan*_*eer 0 c# syntax

我已经编写了一段时间,只是发现了一些有趣的东西.所以通常当我在类中实现事件时,我想在尝试调用它之前检查事件的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框架的一部分,它是一种内置语法.任何洞察这个以及它的用途都会有所帮助.我没有太多的搜索,但没有具体找到任何东西.

Eri*_*ert 6

我在上面留下了一些评论,但我觉得它们非常重要,可以被称为答案.

首先,正如其他人所说,这是空条件运算符.

此版本的代码被认为是糟糕的样式,因为检查后事件可能会变为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)

然后同样添加锁定在addremoveEvent.

这是一种比疾病更糟糕的治疗方法.

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无法移动.

永远不要这样做.同样:如果您处于多线程事件处理程序中,则需要确保在过时调用处理程序,并且您可能不会使用锁来执行此操作.这是一个糟糕的情况; 锁是管理线程的标准机制.

多线程很难.如果您不了解事情可能出错的所有方式,以及如何避免它们,请不要编写多线程程序.