请考虑以下代码段,其中包含泛型和重载函数:
using System;
namespace Test_Project
{
public interface Interface
{
void f();
}
public class U : Interface
{
public void f() {}
}
public class Class<T> where T: Interface
{
public static void OverloadedFunction(T a)
{
Console.WriteLine("T");
a.f();
}
public static void OverloadedFunction(U a)
{
Console.WriteLine("U");
a.f();
}
}
class Program
{
public static void Invoke(U instance)
{
Class<U>.OverloadedFunction(instance);
}
static void Main(string[] args)
{
Invoke(new U());
}
}
}
我会说它不能编译,因为我有两个合适的OverloadedFunction候选方法。然而,它确实打印“U”。
在生成的IL中,我可以看到:
.method public hidebysig static
void Invoke (
class Test_Project.U 'instance'
) cil managed
{
// Method begins at RVA 0x2085
// Code size 7 (0x7)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call void class Test_Project.Class`1<class Test_Project.U>::OverloadedFunction(class Test_Project.U)
IL_0006: ret
} // end of method Program::Invoke
意味着C#编译器将对OverloadedFunction的调用解析为调用,而不是“泛型”函数所需的callvirt。 从编译器的角度来看,我可以猜测'U'方法是一个更好的候选方法,但我无法解释为什么......
我真的很想了解这里发生了什么,我不知道。
但是如果你考虑这个片段的修改版本,我们会引入另一个间接层,那就更奇怪了:
using System;
namespace Test_Project
{
public interface Interface
{
void f();
}
public class U : Interface
{
public void f() {}
}
public class V : U { }
public class Class<T> where T: Interface
{
public static void OverloadedFunction(T a)
{
Console.WriteLine("T");
a.f();
}
public static void OverloadedFunction(U a)
{
Console.WriteLine("U");
a.f();
}
}
class Program
{
public static void Invoke(V instance)
{
Class<V>.OverloadedFunction(instance);
}
static void Main(string[] args)
{
Invoke(new V());
}
}
}
我希望这个程序仍然打印'U',因为'V'是继承的'U'。但它打印'T',如MSIL所示:
.method public hidebysig static
void Invoke (
class Test_Project.V 'instance'
) cil managed
{
// Method begins at RVA 0x208d
// Code size 7 (0x7)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call void class Test_Project.Class`1<class Test_Project.V>::OverloadedFunction(!0)
IL_0006: ret
} // end of method Program::Invoke
意味着c#编译器首选通用版本。
可以请某人解释在通用参数和继承方面,重载方法解析的规则是什么?
答案 0 :(得分:5)
这在C# spec。
中指定在第一种情况下,我们有两个候选人:
1. public static void OverloadedFunction(T (= U) a)
2. public static void OverloadedFunction(U a)
该规范的第7.5.3.6节(强调我的):
虽然声明的签名必须是唯一的,但是类型参数的替换可能会产生相同的签名。在这种情况下,上面超载解决方案的打破平局规则将选择最具体的成员。
打破平局规则(第7.5.3.2节)说:
类型参数的特定性低于非类型参数
T
是一个类型参数; U
不是。因此,U
更具体,选择了过载2。
在第二种情况下,我们有以下两个候选人:
1. public static void OverloadedFunction(T (= V) a)
2. public static void OverloadedFunction(U a)
此处,重载1 T (= V)
是更好的匹配:
V
到V
)比任何其他类型的转换(例如将V
扩展为{{1}更好转换})。答案 1 :(得分:0)
对我来说很有意义。
它将选择最佳匹配,更喜欢不需要铸造的过载。由于您使用的是Class<V>
,因此使用此方法:
public static void OverloadedFunction(T a)
有效地成为:
public static void OverloadedFunction(V a)
在接受V
类型的参数时更好匹配,因为不需要强制转换。
在我看来,你的第一个例子更难以预测,因为任何一个都可行。但它似乎更喜欢强类型方法而不是泛型,我认为这也是有道理的。
阅读规范,看起来似乎是非通用方法:https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/expressions#method-invocations