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

≡放荡痞女 提交于 2021-01-20 04:20:19

问题


Starting from C# 7.0 the throw keyword can be used both as an expression and as a statement, which is nice. Though, consider these overloads

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

When invoking like this

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

or even like this (with a statement lambda)

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

the M(Func<>) overload is selected by the compiler indicating that the throw is here considered as an expression. How can I elegantly and intent-clear force the compiler to select the M(Action) overload?

One way to do it is this

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

but the reason for the return statement seems non-obvious, and runs the risk of being changed by the next developer especially since Resharper warns about the unreachable code.

(Of course I can change the method naming to avoid overloading, but that is not the question. :-)


回答1:


You could add a cast to for Action, although it does get a bit LISP'y with all the parentheses:

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

Not ideal, but if you want maximum clarity:

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



回答2:


This has nothing to do with whether the lambda is a statement lambda or an expression lambda (as is most succinctly shown by you changing the lambda from an expression lambda to a statement lambda and the behavior not changing).

There are numerous ways you can make a lambda match multiple possible overloads. This one is specific to newer versions, but other methods have applied since C# 1.0 (and the specific handling of anonymous methods and the resulting overload resolution disambiguation has needed to exist since the introduction of anonymous methods).

The rules for determining which overload is called are spelled out in section 7.5.3.3 of the C# specs. Specifically, when the parameter is an anonymous method, it will always prefer the overload who's delegate (or expression) has a return value over one that has no return value. This will be true whether it's a statement lambda or expression lambda; it applies to any form of anonymous function.

Thus you either need to prevent both overload from matching by making the anonymous method not valid for a Func<int>, or explicitly force the type to be an Action so the compiler is not disambiguating it itself.




回答3:


One possible approach is to use named parameters:

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());
}

This should probably be documented in the code so as not to surprise the next developer to come along, and as noted in the comments write an appropriate automated test to verify the correct overload is called.




回答4:


To add to all the reasonable answers given, here's a charming unreasonable answer:

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

That one should bake the noodle of any maintenance programmers who come along, and they'll leave it alone. See if you can figure out why it works.



来源:https://stackoverflow.com/questions/54208149/how-can-i-force-a-throw-to-be-a-statement-and-not-an-expression-in-a-lambda-exp

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!