【问题标题】:Overload resolution with generics and implicit conversion使用泛型和隐式转换的重载解决方案
【发布时间】:2021-12-10 10:06:22
【问题描述】:
此代码未编译:
static class Foo
{
public static Bar Do(Func<Bar> f) => null;
public static Bar<TOut> Do<TOut>(Func<Bar<TOut>> f) => null;
}
public class Bar
{
}
public class Bar<TOut>
{
public static implicit operator Bar<TOut>(TOut i) => null;
}
// Here compiler complains:
// CS0029 Cannot implicitly convert type 'int' to 'Bar'
// CS1662 Cannot convert lambda expression to intended delegate type
// because some of the return types in the block
// are not implicitly convertible to the delegate return type
Foo.Do(() => 1);
我的期望是编译器会看到 lambda 的返回类型,并且除非将 int 转换为 Bar<int>,否则无法选择有效的重载。
但是,我看到编译器解析为第一种方法。
规范的哪一部分定义了这种行为?
【问题讨论】:
标签:
c#
generics
implicit-conversion
overload-resolution
【解决方案1】:
这是在Method Invocations 中指定的,当规范讨论哪些方法声明被视为重载解析的候选时,对于M(A) 形式的调用:
构造方法调用的候选方法集。对于与方法组M关联的每个方法F:
- 如果
F 是非通用的,则F 在以下情况下是候选对象:
- 如果
F 是通用的并且M 没有类型参数列表,则F 在以下情况下是候选对象:
- 类型推断成功,推断调用的类型参数列表,并且
- [...]
仅从这些规则中,我们可以看到非泛型 Do 是 候选者,而泛型 Do 不是,因为类型推断失败。尝试注释掉非泛型 Do,您会看到它说“无法推断类型参数”。
【解决方案2】:
我没有完整的答案,但我有一些观察:
观察 1:删除非通用版本的 Do:
static class Foo
{
public static Bar Do(Func<Bar> f) => null;
}
public class Bar
{
public static implicit operator Bar(int i) => null;
}
// CS0411: The type arguments for method 'Foo.Do<TOut>(Func<Bar<TOut>>)' cannot be inferred from the usage. Try specifying the type arguments explicitly.
Foo.Do(() => 1);
,编译器仍然无法解析Func<Bar<TOut>> f。因此,问题似乎不在于选择错误的重载,而是编译器根本无法将() => 1 与Func<Bar<TOut>> f 隐式匹配。
观察 2:下面的代码有效
static class Foo
{
public static Bar Do(Func<Bar> f) => null;
}
public class Bar
{
public static implicit operator Bar(int i) => null;
}
Foo.Do(() => 1);
这表明编译器正在检查隐式转换。
观察 3:使隐式强制转换运算符将int 作为输入,尽管使运算符实际上不可用,因为TOut 将无法解析,使编译器找到运算符:
static class Foo
{
public static Bar<TOut> Do<TOut>(Func<Bar<TOut>> f) => null;
}
public class Bar<TOut>
{
public static implicit operator Bar<TOut>(int i) => null; // Input is `int` now
}
// CS0411: The type arguments for method 'Foo.Do<TOut>(Func<Bar<TOut>>)' cannot be inferred from the usage. Try specifying the type arguments explicitly.
Foo.Do(() => 1);
所以所有这些结合起来让我觉得编译器只是没有尝试哪些泛型类型会导致隐式转换起作用。