操作员 '?'不能应用于 'method group' 类型的操作数

Operator '?' cannot be applied to operand of type 'method group'

这是一个关于 C# 新引入的空值检查运算符的问题。

假设我的界面如下:

interface ILogger
{
    void Log(string message);
}

和一个需要记录操作的函数:

void DoWork(Action<string> logAction)
{
    // Do work and use @logAction
}

如果我尝试编写,为什么会出现以下编译器错误:

void Main(string[] args)
{
    ILogger logger = GetLogger(); // Assume GetLogger() may return null

    //
    // Compiler error: 
    // Operator '?' cannot be applied to operand of type 'method group'
    //
    DoWork(logger?.Log);   
}

这里 ?. 没有什么特别之处,它的工作方式与 ?: 相同:logger?.Loglogger == null ? null : logger.Log 的结果相同,除了logger 仅被评估一次。

问题是 logger == null ? null : logger.Log 在早期版本的 C# 中同样无效。 ?: 要求一个操作数可以转换为另一个操作数的类型,但 nulllogger.Log 都没有类型。你必须把它写成例如logger == null ? null : (Action<string>) logger.Log.

不幸的是,该强制转换的引入意味着您无法使用简单漂亮的 C# 6 缩短版本,这同样适用于 ?.logger?.Log 无效,因为 logger.Log 确实如此没有类型,所以 logger?.Log 也没有类型,但是如果它是一个没有类型的表达式,并且它不是一个方法组,那么 C# 将无能为力。

添加到@hvd 语句

我们还可以使用 空条件运算符 ( ?. )Null-Coalescing 运算符 ( ?? ) 如果你写下面的代码

string userName = null;
int len = userName.Length;

然后

userName.Length

将抛出异常:“'System.NullReferenceException' 类型的未处理异常发生在 ConsoleApplication 中... 附加信息:未将对象引用设置为对象的实例。"

如果你使用下面的代码

string userName = null;
int? len = userName.Length;

你仍然会得到同样的异常

但是

int? len = userName?.Length;

它将return null 并且不会抛出异常

但我们仍有问题。问题是我正在使用 int? (Nullable int) 这不好,因为人们更喜欢如果字符串为 null 那么它应该 return 0.

所以我们可以通过空条件运算符(?.)和null-coalescing运算符(??)

的组合来做到这一点
int len = userName?.Length ?? 0;

已添加

我们还有空条件运算符的另一种用法。

比如当你处理事件时,我们可以使用

myCustomEventHandler?.Invoke(this, EventArgs.Empty);

其中 "myCustomEventHandler" 是 C#6.0 之前的 public 事件,我们必须创建 "myCustomEventHandler" 的本地副本,否则它可能会在 muti-threaded 应用程序的情况下抛出异常。而在 C# 6 中,我们可以直接调用 "myCustomEventHandler" 而无需创建本地副本,并且它是线程安全的。

例如在 C# 5

var handler= this.myCustomEventHandler;
if (handler!= null)
    handler(…)

在 C# 6 中

myCustomEventHandler?.Invoke(e)

如果您可以将界面更改为

interface ILogger
{
    //void Log(string message);
    Action<string> Log {get; }
}

那么你可以使用 logger?.Log 表示法。

ILogger 实现看起来像

class Logger : ILogger
{
    Action<string> Log => str => { /* Do stuff with str */ };
}