带有 return 语句的空合并运算符

问题描述 投票:0回答:2

正如您在 C# 文档中看到的那样,我们可以编写空合并运算符与 throw 表达式组合,如下所示

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

但在很多情况下,如果左值为空,我需要返回语句,例如下面的内容

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

    // Call some method
    AnyMethod(someValue);

    // Some Computation
    return someValue + 17;
}

但我知道我无法编写类似上面的代码,我的问题是为什么?为什么 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; }
    
c# null-coalescing-operator c#-8.0
2个回答
5
投票

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

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

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

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

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

抛出表达式不仅方便,它们还允许在只有表达式有效的地方抛出异常。这就是为什么它们被用在函数式语言中,例如 F# 的

raise

failwith 函数。 如果没有它们,像 C# 的 switch 表达式 或 F# 的 match 表达式 这样的模式匹配结构将无法在编译时编写和分析。 没有抛出表达式:

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 语句并使用 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;
                          }
    };

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

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


0
投票

我确实尝试过{ continue; }

,但我想我理解其他人所说的不良副作用。

空合并运算符不会推断 lambda 的返回类型,因此您必须在其他地方声明它。

选项 A 是合并到内联委托:

double Trivial(string a, string b) { a == b ? 1 : string.IsNullOrEmpty(a) ? 0 : null; } double MyCalc(string a, string b) { double calc(string a, string b) { // non-trivial calculation } return Trivial(a, b) ?? calc(a, b); }

选项 B 是冗长的空合并:
var trivial = Trivial(a, b);

if (trivial.HasValue)
{
    return trivial.Value;
}

// non-trivial calculation


© www.soinside.com 2019 - 2024. All rights reserved.