为什么下面的电话是模棱两可的?

Why following call is ambiguous?

以下调用将失败,因为编译器需要方法 SetAll(PropertyInfo, int)

var infos = GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);

var setters = infos.Select(SetAll); // no overload matches delegate.

private Action SetAll(PropertyInfo info, object obj) => () => info.SetValue(this, obj);

所以这意味着编译器无法以任何方式使用此重载。它不能将 int 转换为 object.

考虑到这一点,为什么以下调用不明确?

var b = infos.Select(SetAll); // ambiguous between Select<PropertyInfo, int, Action>
                              //              and  Select<PropertyInfo, Action>

private Action SetAll(PropertyInfo info, object obj) => () => info.SetValue(this, obj);

private Action SetAll(PropertyInfo info) => () => info.SetValue(this, null);

如果编译器不能以任何方式对对象使用重载,那么为什么它会在这里挣扎?


这是我拥有的实际代码。我可以很容易地处理这个问题,但我只是好奇。

var infos = GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);

if (useDefaultsOnReset)
{
    var defaults = infos.Select(GetAll);
    _resetters = infos.Zip(defaults, SetAll).ToArray();
}
else
{
    _resetters = infos.Select(SetAll).ToArray(); // error
}

private object GetAll(PropertyInfo info) => info.GetValue(this);
private Action SetAll(PropertyInfo info, object obj) => () => info.SetValue(this, obj);
private Action SetAll(PropertyInfo info) => () => info.SetValue(this, null);

这是因为 System.Func<in T1, in T2, out TResult> 的参数类型是逆变的。这由相应类型参数上的 in 修饰符表示。这意味着它匹配任何接受类型 T1 或任何类型 T1 的参数的函数,以及类型 T2 或任何类型的参数 T2可以赋值给。您的第一个签名与不包含索引的 Enumerable.Select 的重载相匹配。但是,您的第二个签名实际上匹配 Enumerable.Select 的重载,它包含索引,因为 int 可分配给 object.

为了证明这一点。只需创建一个任意的 class 并像这样更改您的程序。

private Action SetAll(PropertyInfo info, A a) => () => info.SetValue(this, obj);
private Action SetAll(PropertyInfo info) => () => info.SetValue(this, null);

class A {}

您会发现错误消失了,因为 int 无法分配给 A

正如评论中所讨论的,我没有考虑到一个问题。逆变关系在引用类型之间和不限于值类型的泛型之间存在,但在采用 intobject 的委托之间直接分配时尤其不起作用 鉴于

Func<int, Action> f;

Func<object, Action> g;

以下是两个错误

g = f;
f = g;

但是,如果我们将 int 替换为 class A

Func<A, Action> f;

Func<object, Action> g;

第一个是错误,因为对象不是 A,但第二个成功,如上所述。

g = f;
f = g;

为了使用方法组,您可以编写虚拟变量。

private Action SetAll(PropertyInfo info, object obj) => () => info.SetValue(this, obj);
private Action SetAll(PropertyInfo info, int _) => () => info.SetValue(this, null); 
//                                           ^ this is dummy but lets you use methodgroup

这样就可以了

infos.Select(SetAll).ToArray(); 

它将 Select 与索引器一起使用,但应该没什么大不了的。