从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 <>)重载,指示throw在这里被视为表达式。如何优雅和明确地强制编译器选择M(Action)重载?
一种方法是这样做
M(() => { throw new Exception(); return; });
但返回语句的原因似乎并不明显,并且存在被下一个开发人员更改的风险,特别是因为Resharper警告无法访问的代码。
(当然我可以更改方法命名以避免重载,但这不是问题。:-)
你可以为Action
添加一个强制转换,虽然它的所有括号都有点LISP'y:
M((Action)(() => throw new Exception()));
不理想,但如果你想要最大程度的清晰度:
Action thrw = () => throw new Exception();
M(thrw);
这与lambda是一个语句lambda还是一个表达式lambda无关(你最简洁地将lambda从一个表达式lambda更改为一个语句lambda并且行为没有改变)。
有许多方法可以使lambda匹配多个可能的重载。这个特定于较新的版本,但是自C#1.0以来已经应用了其他方法(并且自匿名方法的引入以来,匿名方法的特定处理以及由此产生的重载消除歧义消除已经存在)。
确定调用哪个重载的规则在C#规范的第7.5.3.3节中详细说明。具体来说,当参数是匿名方法时,它总是更喜欢重载谁的委托(或表达式)具有返回值而不是没有返回值的返回值。无论是语句lambda还是表达式lambda,都是如此;它适用于任何形式的匿名函数。
因此,您需要通过使匿名方法对Func<int>
无效来防止匹配重载,或者明确强制该类型为Action
,以便编译器本身不消除歧义。
一种可能的方法是使用命名参数:
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());
}
这应该记录在代码中,以免下一个开发人员出现,并且如评论中所述,编写一个适当的自动化测试来验证正确的重载被调用。
为了增加给出的所有合理答案,这是一个迷人的不合理的答案:
((Action<Action>)M)(() => throw new Exception());
对于任何维护程序员来说,这应该是bake the noodle,他们会不管它。看看你能否找出它的工作原理。