带 return 语句的空合并运算符

sor*_*abz 2 c# null-coalescing-operator c#-8.0

正如您在C# 文档中所看到的,我们可以将 null 合并运算符与 throw 表达式结合起来,如下所示

public string Name
{
    get => name;
    set => name = value ?? 
        throw new ArgumentNullException(paramName: nameof(value), message: "Name cannot be null");
}   
Run Code Online (Sandbox Code Playgroud)

但在很多情况下,如果左值为空,我需要返回语句,例如如下所示

public double CalculateSomthing(ClassType someInstance)
{
    var someValue = someInstance?.Value ?? return double.NaN;

    // Call some method
    AnyMethod(someValue);

    // Some Computation
    return someValue + 17;
}
Run Code Online (Sandbox Code Playgroud)

但我知道我不能编写类似上面的代码,我的问题是为什么?为什么 C# 语言设计者允许使用 null 合并运算符编写 throw 表达式,但不允许使用带有合并运算符的 return 语句?

它们之间有什么本质区别吗?

在 C# 的现有版本中是否有类似上面示例的类似解决方案?或者我必须编写一些样板代码,如下所示

public double CalculateSomthing(ClassType someInstance)
{

    var someValue = someInstance?.Value;
    if (someValue == null) return double.NaN;  // Boiler plate

    // Call some method
    AnyMethod(someValue);

    // Some Computation
    return someValue + 17;
}
Run Code Online (Sandbox Code Playgroud)

Pan*_*vos 5

返回/中断/继续表达式 已经被讨论过,但很难正确实现,因为它们会干扰其他构造。充其量,这是一个便利功能。正如设计会议笔记所说:

这是一个方便。然而,它存在与其他潜在的 future 发生语法冲突的风险,尤其是“非本地返回”(允许 lambda 从其封闭方法返回)和“块表达式”(允许表达式内有语句)。虽然我们可以想象那些不冲突的语法,但我们此时不想限制它们的设计空间,至少对于仅仅是“拥有就好”的功能而言。

另外,虽然我们一直在与 throw 表达式类比地讨论这一点,但这并不完全正确。throw 是动态效果,而 return、break 和 continue 是与特定目标静态绑定的控制传输。

正如讨论所示,虽然已经有很多替代方案,但很难提出一个令人信服的例子。

无论如何,返回表达式与抛出表达式都不相似。异常既不是控制流也不是返回机制。这是一个熔断的保险丝,需要处理,否则应用程序将无法继续。

抛出表达式不仅方便,而且还允许在只有表达式有效的地方抛出异常。这就是为什么它们被用在函数式语言中,例如 F# 的raisefailurewith函数。如果没有它们,模式匹配结构(例如 C# 的switch 表达式或 F# 的匹配表达式)将无法在编译时编写和分析。

没有抛出表达式:

public static RGBColor FromRainbow(Rainbow colorBand) =>
    colorBand switch
    {
        Rainbow.Red    => new RGBColor(0xFF, 0x00, 0x00),
        Rainbow.Orange => new RGBColor(0xFF, 0x7F, 0x00),
        _              => throw new ArgumentException(message: "invalid enum value", paramName: nameof(colorBand)),
    };
Run Code Online (Sandbox Code Playgroud)

必须重写以包含虚拟返回语句,从而使编译期间的代码分析变得不必要的困难。编译器必须识别这种模式,忽略 return 语句并使用 throw 语句来验证代码是否有效:

public static RGBColor FromRainbow(Rainbow colorBand) =>
    colorBand switch
    {
        Rainbow.Red    => new RGBColor(0xFF, 0x00, 0x00),
        Rainbow.Orange => new RGBColor(0xFF, 0x7F, 0x00),
        _              => { 
                              throw new ArgumentException(message: "invalid enum value", paramName: nameof(colorBand));
                              return default;
                          }
    };
Run Code Online (Sandbox Code Playgroud)

return default也会对 C# 8 的可为空引用类型和可为空分析造成严重破坏。

C# 8 利用 C# 7 的 throw 表达式来提供 switch 表达式。C# 9 将利用两者来提供可区分的联合和(希望如此)详尽的模式匹配。