使用泛型和类型推断的 C# 方法解析
C# method resolution with generic and type inference
今天我对方法解析的工作方式感到惊讶。
示例代码如下:
class Program
{
static class Mapper<TSource, TTarget>
{
public static void Map<TMember>(Expression<Func<TSource, TMember>> source, Expression<Func<TTarget, TMember>> target)
{
Console.WriteLine("A");
}
public static void Map<TMember, TSourceCollection>(Expression<Func<TSource, TSourceCollection>> source, Expression<Func<TTarget, TMember[]>> target)
where TSourceCollection : IEnumerable<TMember>
{
Console.WriteLine("B");
}
}
class A
{
public byte[] prop { get; set; }
}
class B
{
public byte[] prop { get; set; }
}
static void Main(string[] args)
{
Mapper<A, B>.Map(x => x.prop, x => x.prop);
}
}
如你所见,Map 方法有两个重载,一个是属性类型相同的重载,一个是源 属性 是一个可枚举的,右边的 属性 是一个数组.
然后,当我调用方法时,两侧都有一个数组,它会调用第二个重载,但由于类型完全相同,我希望调用第一个重载。
我认为第一个重载的得分会更高,因为它依赖于比第二个更少的通用参数,并且它更适合我传递给方法的参数类型。
有人可以解释为什么编译器选择调用第二个重载而不是第一个重载吗?
谢谢。
第一种方法TMember
和第二种方法TSourceCollection
的匹配对于任何满足where TSourceCollection : IEnumerable<TMember>
条件的类型都是等价的
与 TMember
相比,类型 TMember[]
是 byte[]
的更详细的类型匹配。所以这应该是第二种方法比第一种方法得分更好的地方。因此,这个 'better' 匹配排除了方法二具有更多通用参数的事实。
过载解决方案很复杂,您可以阅读规范以了解原因。不过,我非常确定的一件事是,在考虑哪个重载更好时,它不考虑指定较少泛型参数的需要(尽管它会将非泛型置于泛型之上,但当两者都是泛型时,它认为它们是相等的)。
在查看重载时,它可以选择它们都相等,除了第二个参数是 TMember
还是 TMember[]
。
规范在选择最具体的成员方面谈了很多,我无法弄清楚哪一部分在这里实际适用(有很多地方谈到当 X 更具体时更喜欢 X 而不是 Y)。我原以为它是第 7.6.5.1 节(c#5 规范),它构建了一个候选列表,或者是第 7.5.3 节,它处理了重载决议。然而,前者似乎并不排除任何一种方法重载,而根据我的阅读,后者仅处理通用参数被替换后的参数,此时它们是相同的。规范中可能有其他地方处理此问题(例如,当它推断类型参数时)。
虽然我相信编译器正在考虑 TMember[]
比 TMember
更具体,但用粗略的术语来说。这可以被广泛认为是正确的,因为 TMember
会比 TMember[] 接受更多的东西,所以 TMember[]
更具体。
今天我对方法解析的工作方式感到惊讶。
示例代码如下:
class Program
{
static class Mapper<TSource, TTarget>
{
public static void Map<TMember>(Expression<Func<TSource, TMember>> source, Expression<Func<TTarget, TMember>> target)
{
Console.WriteLine("A");
}
public static void Map<TMember, TSourceCollection>(Expression<Func<TSource, TSourceCollection>> source, Expression<Func<TTarget, TMember[]>> target)
where TSourceCollection : IEnumerable<TMember>
{
Console.WriteLine("B");
}
}
class A
{
public byte[] prop { get; set; }
}
class B
{
public byte[] prop { get; set; }
}
static void Main(string[] args)
{
Mapper<A, B>.Map(x => x.prop, x => x.prop);
}
}
如你所见,Map 方法有两个重载,一个是属性类型相同的重载,一个是源 属性 是一个可枚举的,右边的 属性 是一个数组.
然后,当我调用方法时,两侧都有一个数组,它会调用第二个重载,但由于类型完全相同,我希望调用第一个重载。
我认为第一个重载的得分会更高,因为它依赖于比第二个更少的通用参数,并且它更适合我传递给方法的参数类型。
有人可以解释为什么编译器选择调用第二个重载而不是第一个重载吗?
谢谢。
第一种方法TMember
和第二种方法TSourceCollection
的匹配对于任何满足where TSourceCollection : IEnumerable<TMember>
条件的类型都是等价的
与 TMember
相比,类型 TMember[]
是 byte[]
的更详细的类型匹配。所以这应该是第二种方法比第一种方法得分更好的地方。因此,这个 'better' 匹配排除了方法二具有更多通用参数的事实。
过载解决方案很复杂,您可以阅读规范以了解原因。不过,我非常确定的一件事是,在考虑哪个重载更好时,它不考虑指定较少泛型参数的需要(尽管它会将非泛型置于泛型之上,但当两者都是泛型时,它认为它们是相等的)。
在查看重载时,它可以选择它们都相等,除了第二个参数是 TMember
还是 TMember[]
。
规范在选择最具体的成员方面谈了很多,我无法弄清楚哪一部分在这里实际适用(有很多地方谈到当 X 更具体时更喜欢 X 而不是 Y)。我原以为它是第 7.6.5.1 节(c#5 规范),它构建了一个候选列表,或者是第 7.5.3 节,它处理了重载决议。然而,前者似乎并不排除任何一种方法重载,而根据我的阅读,后者仅处理通用参数被替换后的参数,此时它们是相同的。规范中可能有其他地方处理此问题(例如,当它推断类型参数时)。
虽然我相信编译器正在考虑 TMember[]
比 TMember
更具体,但用粗略的术语来说。这可以被广泛认为是正确的,因为 TMember
会比 TMember[] 接受更多的东西,所以 TMember[]
更具体。