如何强制 throw 成为语句而不是表达式(在 lambda 表达式中)?

How can I force a throw to be a statement and not an expression (in a lambda expression)?

从 C# 7.0 开始,throw 关键字既可以用作表达式,也可以用作语句,这很好。 不过,考虑这些重载

public static void M(Action doIt) { /*use doIt*/ }
public static void M(Func<int> doIt) { /*use doIt*/ }

像这样调用时

M(() => throw new Exception());

甚至像这样(使用 lambda 语句)

M(() => { throw new Exception(); });

M(Func<>) 重载由编译器 select 编辑,表明此处抛出被视为表达式。 我怎样才能优雅和明确地强制编译器 select M(Action) 重载?

一种方法是这样

M(() => { throw new Exception(); return; });

但是 return 语句的原因似乎并不明显,并且存在被下一个开发人员更改的风险,特别是因为 Resharper 警告无法访问代码。

(当然我可以更改方法命名以避免重载,但这不是问题所在。:-)

您可以为 Action 添加强制转换,尽管它确实有点 LISP'y 所有括号:

M((Action)(() => throw new Exception()));

不理想,但如果您想要最大程度的清晰度:

Action thrw = () => throw new Exception();
M(thrw);

一种可能的方法是使用命名参数:

public static void M(Action action) { /* do stuff */ }

public static void M(Func<int> func) { /* do stuff */ }

public static void Main()
{
    M(action: () => throw new Exception());
}

这可能应该记录在代码中,以免让下一位开发人员感到惊讶,并且如评论中所述,编写适当的自动化测试以验证是否调用了正确的重载。

这与 lambda 是语句 lambda 还是表达式 lambda 无关(最简洁的表现是将 lambda 从表达式 lambda 更改为语句 lambda,并且行为没有改变)。

有多种方法可以使 lambda 匹配多个可能的重载。这一个特定于较新的版本,但自 C# 1.0 以来应用了其他方法(自引入匿名方法以来,需要存在匿名方法的特定处理和由此产生的重载解析消歧)。

C# 规范的第 7.5.3.3 节详细说明了确定调用哪个重载的规则。具体来说,当参数是匿名方法时,它总是更喜欢重载的委托(或表达式)具有 return 值而不是没有 return 值的重载。无论是语句 lambda 还是表达式 lambda 都是如此;它适用于任何形式的匿名函数。

因此,您需要通过使匿名方法对 Func<int> 无效来防止两个重载匹配,或者明确强制类型为 Action,这样编译器就不会消除歧义本身。

除了给出的所有合理答案之外,还有一个迷人的不合理答案:

((Action<Action>)M)(() => throw new Exception());

任何一起来的维护程序员都应该 bake the noodle,他们不会管它。看看你能不能弄清楚它为什么有效。