为什么 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。涉及到比较多的类型推断算法,这里就不详细解释了。我希望你会同意 直觉上 ,Guid
和 SomeWrapper
之间最常见的类型是 SomeWrapper
,因为 Guid
可以转换为 SomeWrapper
.
因此,Task.Run
的通用参数被推断为 SomeWrapper
,您会得到预期的 Task<SomeWrapper>
。
要使表达式体 lambda 起作用,您只需为 Task.Run
指定类型参数:
Task.Run<SomeWrapper>(() => Guid.NewGuid())
考虑以下结构:
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 ofF
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 typeT
, then the inferred result type ofF
isT
.- 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。涉及到比较多的类型推断算法,这里就不详细解释了。我希望你会同意 直觉上 ,Guid
和 SomeWrapper
之间最常见的类型是 SomeWrapper
,因为 Guid
可以转换为 SomeWrapper
.
因此,Task.Run
的通用参数被推断为 SomeWrapper
,您会得到预期的 Task<SomeWrapper>
。
要使表达式体 lambda 起作用,您只需为 Task.Run
指定类型参数:
Task.Run<SomeWrapper>(() => Guid.NewGuid())