为什么这个Assert.Throws调用这样解决?

jer*_*enh 9 c# xunit

使用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)

Jon*_*eet 3

这是一个不涉及的完整示例Task,以删除涉及异步的任何提示:

\n\n
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}\n
Run Code Online (Sandbox Code Playgroud)\n\n

使用ECMA 334(第 5 版)中的节编号,我们对第 12.6.4 节 - 重载解析感兴趣。两个重要的步骤是:

\n\n
    \n
  • 确定适用的方法(12.6.4.2)
  • \n
  • 确定最佳方法 (12.6.4.3)
  • \n
\n\n

我们将依次查看每个调用

\n\n

致电 1:() => ThrowException()

\n\n

让我们从第一个调用开始,它的参数为() => ThrowException()。为了检查适用性,我们需要从该参数转换为Actionor Func<int>。我们可以在不涉及重载的情况下检查这一点:

\n\n
// 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();\n
Run Code Online (Sandbox Code Playgroud)\n\n

在这种情况下,CS1662 错误的措辞有点不幸 - 并不是块中存在不能隐式转换为委托返回类型的返回类型,而是没有lambda 表达式中的返回类型。防止这种情况的规范方法在第 11.7.1 节中。那里允许的转换都不起作用。最接近的是这个(其中 F 是 lambda 表达式,D 是Func<int>):

\n\n
\n

如果 的主体F是一个表达式,并且要么F是非异步的并且D具有非 void 返回类型T,要么F是异步的并且D具有返回类型Task<T>,那么当 F 的每个参数被赋予 中相应参数的类型时D,主体ofF是一个有效的表达式 (wrt \xc2\xa712),可隐式转换为T.

\n
\n\n

在这种情况下,表达式ThrowException不能隐式转换为,因此会出现int错误。

\n\n

所有这些意味着只有第一种方法适用于() => ThrowException(). 当适用的函数成员集只有一个条目时,我们选择“最佳函数成员”真的很容易......

\n\n

致电 2:() => { throw new Exception(); }

\n\n

现在让我们看看第二个调用,它有() => { throw new Exception(); }作为参数。让我们尝试相同的转换:

\n\n
// Fine\nAction action = () => { throw new Exception(); };\n// Fine\nFunc<int> func = () => { throw new Exception(); };\n
Run Code Online (Sandbox Code Playgroud)\n\n

两种转换都在这里进行。后一个之所以有效,是因为11.7.1 中的这个项目符号:

\n\n
\n

如果 的主体F是语句块,并且要么F是非异步的并且D具有非 void 返回类型T,要么F是异步的并且D具有返回类型Task<T>,则当为 的每个参数F指定相应参数的类型时in\n D的主体F是一个有效的语句块(wrt \xc2\xa713.3),具有不可到达的端点,其中每个 return 语句指定一个可隐式转换为 的表达式T

\n
\n\n

我意识到这听起来很奇怪,但是:

\n\n
    \n
  • 块的终点无法到达
  • \n
  • 没有 return 语句,因此确实满足“每个 return 语句指定 [...]”的条件
  • \n
\n\n

换句话说:您可以使用该块作为声明为 return 的方法的主体int

\n\n

这意味着我们的两种方法都适用于这种情况。

\n\n

那么哪个更好呢?

\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
  • E 是匿名函数,T1 是委托类型 D1 或表达式树\n 类型Expression<D1>,T2 是委托类型 D2 或表达式树类型Expression<D2>,并且\n 满足以下条件之一:\n \n
      \n
    • D1 是比 D2 更好的转化目标
    • \n
    • D1 和 D2 具有相同的参数列表,并且满足以下条件之一:\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
\n
\n\n

我用粗体显示的部分是重要的部分。当您考虑以下场景时:

\n\n
    \n
  • E 是() => { throw new Exception(); }
  • \n
  • T1 是Func<int>(所以 D1Func<int>也是)
  • \n
  • T2 是Action(所以 D2 也是Action
  • \n
\n\n

...那么 D1 和 D2 都有空参数列表,但 D1 有返回类型int,而 D2 是 void 返回。

\n\n

因此,转换为Func<int>比转换为Action... 更好,这意味着它是比第二次调用Method(Action)更好的函数成员。Member(Func<int>)

\n\n

唷!难道你不喜欢重载解析吗?

\n