正如您在 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;
}
返回/中断/继续表达式已经进行了讨论,但很难正确实现,因为它们会干扰其他结构。充其量,这是一个便利功能。正如设计会议笔记所说:
这是一个方便。然而,它存在与其他潜在的 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 将利用两者来提供可区分的联合和(希望如此)详尽的模式匹配。
我确实尝试过{ 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