重载,泛型和类型约束:方法解析

Reg*_*lez 4 c# generics inheritance overloading

请考虑以下代码段,其中包含泛型和重载函数:

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());
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我会说它不会编译,因为我有两个合适的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
Run Code Online (Sandbox Code Playgroud)

这意味着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());
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我希望这个程序仍然打印'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
Run Code Online (Sandbox Code Playgroud)

这意味着通用版本已被c#编译器所优选.

有人可以解释一下,当涉及到泛型参数和继承时,重载方法解析的规则是什么?

Hei*_*nzi 5

这在C#规范中指定.


在第一种情况下,我们有两个候选人:

1. public static void OverloadedFunction(T (= U) a)
2. public static void OverloadedFunction(U a)
Run Code Online (Sandbox Code Playgroud)

规范的第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)
Run Code Online (Sandbox Code Playgroud)

在这里,重载1 T (= V)是一个更好的匹配:

  • 根据§7.5.3.3,身份转换(Vto V)是比任何其他类型的转换(例如扩展VU)更好的转换.
  • 因此,根据§7.5.3.2,对于此调用,重载1是"更好的函数成员"而不是重载2.