为什么 C# 编译器不调用隐式运算符。编译器错误?

Why C# compiler doesn't call implicit operator. Compiler bug?

考虑以下结构:

struct SomeWrapper
{
    public Guid guid;

    public static implicit operator SomeWrapper(Guid guid) => new SomeWrapper {guid = guid};
}

这个结构定义了一个隐式运算符,用于将 Guid 视为 SomeWrapper,非常简单。 除第一个 PleaseDoNotCompile:

外,以下所有方法均可编译
static Task<SomeWrapper> PleaseDoNotCompile() => Task.Run(() => Guid.NewGuid());

static Task<SomeWrapper> WhyDoYouCompile() => Task.Run(() =>
{
    return Guid.NewGuid();

    return new SomeWrapper();
});

static SomeWrapper IUnderstandWhyYouCompile() => Guid.NewGuid();

static async Task<SomeWrapper> IUnderstandWhyYouCompileToo() => await Task.Run(() => Guid.NewGuid());

特别是,WhyDoYouCompile 只是第一个带有附加 return 语句 returning SomeWrapper 值的方法。很明显 return 是不可达的代码。而且它仍然可以编译,而第一个没有。

因此,除了附加的 return 语句之外,这两种方法之间实际上还有另一个区别:PleaseDoNotCompile 使用 Task.Run<Guid>(Func<Guid> function)WhyDoYouCompile 使用 Task.Run<SomeWrapper>(Func<SomeWrapper> function).因此,额外的 return 实际上改变了使用的覆盖。

尽管如此,IUnderstandWhyYouCompileToo 只是带有 async 和 await 关键字的 PleaseDoNotCompile 也使用了 Task.Run<Guid>(Func<Guid> function) 并且它确实可以编译。

所以,问题很简单,为什么 PleaseDoNotCompile 不能编译而其他方法可以编译?我错过了什么?

这在语言规范的 Inferred Return Type 部分进行了解释。

在类型推断期间,编译器必须弄清楚您传递给 Task.Run 的 lambda 的 return 类型是什么,以便推断 Task.Run 的泛型参数。规则是(对于 lambda F):

  • If the body of F is an expression that has a type, then the inferred result type of F is the type of that expression.
  • If the body of F is a block and the set of expressions in the block's return statements has a best common type T, then the inferred result type of F is T.
  • Otherwise, a result type cannot be inferred for F.

对于 PleaseDoNotCompile,第 1 点适用,并且 return 类型被推断为 Guid,因此 Task<Guid> 由 return 编辑=12=]。请注意,您正在分配给 Task<SomeWrapper> 的事实没有被考虑在内,就像在类型推断中一样。例如:

static void Main(string[] args)
{
    string t = F(); // cannot infer type!
}

public static T F<T>()
{
    return default(T);
}

WhyDoYouCompile中,第二点适用,编译器在Guid.NewGuid()new SomeWrapper()之间找到一个“最佳公共类型”。即使第二个 return 不可达,编译器仍会在此过程中考虑它。我知道这听起来很傻,但这就是规范!

寻找最佳通用类型的规则已指定here。涉及到比较多的类型推断算法,这里就不详细解释了。我希望你会同意 直觉上 GuidSomeWrapper 之间最常见的类型是 SomeWrapper,因为 Guid 可以转换为 SomeWrapper.

因此,Task.Run 的通用参数被推断为 SomeWrapper,您会得到预期的 Task<SomeWrapper>

要使表达式体 lambda 起作用,您只需为 Task.Run 指定类型参数:

Task.Run<SomeWrapper>(() => Guid.NewGuid())