使用xUnit Assert.Throws,我偶然发现(对我来说)难以解释重载解析问题.在xUnit中,此方法已标记为已过时:
[Obsolete("You must call Assert.ThrowsAsync<T> (and await the result) " +
"when testing async code.", true)]
public static T Throws<T>(Func<Task> testCode) where T : Exception
{ throw new NotImplementedException(); }
Run Code Online (Sandbox Code Playgroud)
问题是,为什么简单地抛出异常的内联语句lambda(或表达式)会解析此重载(因此不能编译)?
using System;
using Xunit;
class Program
{
static void Main(string[] args)
{
// this compiles (of course)
// resolves into the overload accepting an `Action`
Assert.Throws<Exception>(() => ThrowException());
// line below gives error CS0619 'Assert.Throws<T>(Func<Task>)' is obsolete:
// 'You must call Assert.ThrowsAsync<T> (and await the result) when testing async code.'
Assert.Throws<Exception>(() => { throw new Exception(); });
}
static void ThrowException()
{
throw new Exception("Some message");
}
}
Run Code Online (Sandbox Code Playgroud)
这是一个不涉及的完整示例Task,以删除涉及异步的任何提示:
using System;\n\nclass Program\n{\n static void Method(Action action)\n {\n Console.WriteLine("Action");\n }\n\n static void Method(Func<int> func)\n {\n Console.WriteLine("Func<int>");\n }\n\n static void ThrowException()\n {\n throw new Exception();\n }\n\n static void Main()\n {\n // Resolvse to the Action overload\n Method(() => ThrowException());\n // Resolves to the Func<int> overload\n Method(() => { throw new Exception(); }); \n }\n}\nRun Code Online (Sandbox Code Playgroud)\n\n使用ECMA 334(第 5 版)中的节编号,我们对第 12.6.4 节 - 重载解析感兴趣。两个重要的步骤是:
\n\n我们将依次查看每个调用
\n\n() => ThrowException()让我们从第一个调用开始,它的参数为() => ThrowException()。为了检查适用性,我们需要从该参数转换为Actionor Func<int>。我们可以在不涉及重载的情况下检查这一点:
// Fine\nAction action = () => ThrowException();\n// Fails to compile:\n// error CS0029: Cannot implicitly convert type \'void\' to \'int\'\n// error CS1662: Cannot convert lambda expression to intended delegate type because \n// some of the return types in the block are not implicitly convertible to the \n// delegate return type\nFunc<int> func = () => ThrowException();\nRun Code Online (Sandbox Code Playgroud)\n\n在这种情况下,CS1662 错误的措辞有点不幸 - 并不是块中存在不能隐式转换为委托返回类型的返回类型,而是没有lambda 表达式中的返回类型。防止这种情况的规范方法在第 11.7.1 节中。那里允许的转换都不起作用。最接近的是这个(其中 F 是 lambda 表达式,D 是Func<int>):
\n\n\n如果 的主体
\nF是一个表达式,并且要么F是非异步的并且D具有非 void 返回类型T,要么F是异步的并且D具有返回类型Task<T>,那么当 F 的每个参数被赋予 中相应参数的类型时D,主体ofF是一个有效的表达式 (wrt \xc2\xa712),可隐式转换为T.
在这种情况下,表达式ThrowException不能隐式转换为,因此会出现int错误。
所有这些意味着只有第一种方法适用于() => ThrowException(). 当适用的函数成员集只有一个条目时,我们选择“最佳函数成员”真的很容易......
() => { throw new Exception(); }现在让我们看看第二个调用,它有() => { throw new Exception(); }作为参数。让我们尝试相同的转换:
// Fine\nAction action = () => { throw new Exception(); };\n// Fine\nFunc<int> func = () => { throw new Exception(); };\nRun Code Online (Sandbox Code Playgroud)\n\n两种转换都在这里进行。后一个之所以有效,是因为11.7.1 中的这个项目符号:
\n\n\n\n\n如果 的主体
\nF是语句块,并且要么F是非异步的并且D具有非 void 返回类型T,要么F是异步的并且D具有返回类型Task<T>,则当为 的每个参数F指定相应参数的类型时in\nD的主体F是一个有效的语句块(wrt \xc2\xa713.3),具有不可到达的端点,其中每个 return 语句指定一个可隐式转换为 的表达式T。
我意识到这听起来很奇怪,但是:
\n\n换句话说:您可以使用该块作为声明为 return 的方法的主体int。
这意味着我们的两种方法都适用于这种情况。
\n\n现在我们需要查看第 12.6.4.3 节来确定实际选择哪种方法。
\n\n这里有很多Action规则,但决定事情的规则是从 lambda 表达式到or 的转换Func<int>。这个问题在 12.6.4.4 中得到了解决(更好的表达式转换):
\n\n\n给定从表达式 E 转换为类型 T1 的隐式转换 C1 和从表达式 E 转换为类型 T2 的隐式转换 C2,如果满足以下至少一项,则 C1 是比 C2 更好的转换:
\n\n\n
\n- ...
\n- E 是匿名函数,T1 是委托类型 D1 或表达式树\n 类型
\nExpression<D1>,T2 是委托类型 D2 或表达式树类型Expression<D2>,并且\n 满足以下条件之一:\n \n\n
- D1 是比 D2 更好的转化目标
\n- D1 和 D2 具有相同的参数列表,并且满足以下条件之一:\n \n
\n\n
- D1 具有返回类型 Y1,D2 具有返回类型 Y2,在该参数列表 (\xc2\xa712.6.3.13) 的上下文中存在 E 的推断返回类型 X,并且从 X 到 Y1 的转换更好比从 X 到 Y2 的转换
\n- E 是异步的 [... - 跳过,因为它不是]
\n- D1 的返回类型为 Y,D2 为 void 返回
\n
我用粗体显示的部分是重要的部分。当您考虑以下场景时:
\n\n() => { throw new Exception(); }Func<int>(所以 D1Func<int>也是)Action(所以 D2 也是Action)...那么 D1 和 D2 都有空参数列表,但 D1 有返回类型int,而 D2 是 void 返回。
因此,转换为Func<int>比转换为Action... 更好,这意味着它是比第二次调用Method(Action)更好的函数成员。Member(Func<int>)
唷!难道你不喜欢重载解析吗?
\n