Overloading, generics and type restrictions: method resolution

Consider this code snippet showing generics and overloaded functions:

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());
        }
    }
}

I would say that it does not compile, since I have two suitable methods for OverloadedFunction. However, he does and prints "U".

In the generated IL, I see that:

.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

means that the C # compiler allowed the call to OverloadedFunction to a call instead of a callvirt, which would require a "common" function. I can guess that the "U" method is the best candidate from a compiler perspective, but I can’t explain exactly why ...

I would really like to understand what happened here, and I have no idea.

But it becomes even stranger if you consider this modified version of the fragment, where another level of indirection is introduced:

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

, #.

- , , ?

+4
2

# 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) :

  • . 7.5.3.3 (V to V) , (, V U).
  • , § 7.5.3.2, 1 " ", 2 .
+5

.

, , . 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

0

Source: https://habr.com/ru/post/1693578/


All Articles