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

Posted

技术标签:

【中文标题】为啥 C# 编译器不调用隐式运算符。编译器错误?【英文标题】:Why C# compiler doesn't call implicit operator. Compiler bug?为什么 C# 编译器不调用隐式运算符。编译器错误? 【发布时间】:2021-09-07 13:43:45 【问题描述】:

考虑以下结构:

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 语句返回 SomeWrapper 值的方法。很明显,return 是无法访问的代码。而且它仍然可以编译,而第一个没有。

因此,除了额外的 return 语句之外,这两种方法之间实际上还有另一个区别:PleaseDoNotCompile 使用 Task.Run&lt;Guid&gt;(Func&lt;Guid&gt; function)WhyDoYouCompile 使用 Task.Run&lt;SomeWrapper&gt;(Func&lt;SomeWrapper&gt; function)。因此,额外的回报实际上改变了所使用的过载。

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

那么,问题就是为什么PleaseDoNotCompile 不能编译而其他方法可以编译?我错过了什么?

【问题讨论】:

因为Task&lt;Guid&gt;不能转换成Task&lt;SomeWrapper&gt; 【参考方案1】:

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

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

如果F 的主体是具有类型的表达式,则F 的推断结果类型就是该表达式的类型。 如果F 的主体是一个块,并且块的返回语句中的表达式集具有最佳公共类型T,则推断结果类型 FT。 否则,无法为F 推断结果类型。

对于PleaseDoNotCompile,第1点适用,返回类型推断为Guid,因此Task&lt;Guid&gt;Task.Run返回。请注意,您分配给 Task&lt;SomeWrapper&gt; 的事实并未像通常在类型推断中一样被考虑在内。例如:

static void Main(string[] args)

    string t = F(); // cannot infer type!


public static T F<T>()

    return default(T);

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

查找最佳通用类型的规则指定为here。涉及到相当多的类型推断算法,这里就不详细解释了。我希望您会同意直观地GuidSomeWrapper 之间最常见的类型是 SomeWrapper,因为 Guid 可以转换为 SomeWrapper

因此,Task.Run 的泛型参数被推断为SomeWrapper,并且您得到了预期的Task&lt;SomeWrapper&gt;

要让表达式主体的 lambda 工作,您可以简单地为 Task.Run 指定类型参数:

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

【讨论】:

以上是关于为啥 C# 编译器不调用隐式运算符。编译器错误?的主要内容,如果未能解决你的问题,请参考以下文章

C# - 为啥我不能在字符串中使用三元运算符? [复制]

Visual Studio 2003 C#,结构编译错误

为啥将class反编译为java后,java直接编译时有错误

隐式运算符 != 解决运算符中的歧义 ==

为啥编译器选择字符串而不是隐式字符数组的扩展方法?

如何使用反射调用自定义运算符