为什么下面的电话是模棱两可的?
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
。
正如评论中所讨论的,我没有考虑到一个问题。逆变关系在引用类型之间和不限于值类型的泛型之间存在,但在采用 int
和 object
的委托之间直接分配时尤其不起作用
鉴于
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
与索引器一起使用,但应该没什么大不了的。
以下调用将失败,因为编译器需要方法 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
。
正如评论中所讨论的,我没有考虑到一个问题。逆变关系在引用类型之间和不限于值类型的泛型之间存在,但在采用 int
和 object
的委托之间直接分配时尤其不起作用
鉴于
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
与索引器一起使用,但应该没什么大不了的。