4

Consider this code snippet, featuring 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 it doesn't compile, as I have two suitable candidates methods for OverloadedFunction. However it does and prints "U".

In the generated IL, I can 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

meaning that the C# compiler resolved the call to OverloadedFunction to a call instead of the callvirt which would have been required by the "generic" function. I can guess that the 'U' method is a better candidate from a compiler perspective, but I can't definitely explain why...

I'd really like to understand what happened here and I have no clue.

But it gets even weirder if you consider this modified version of the snippet, where we introduce another level of indirection :

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

I would expect this program to still print 'U', as 'V' are 'U' by inheritance. But it prints 'T', as show by the 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

meaning that the generic version has been preferred by the c# compiler.

Could please someone explain what are the rules of overloaded methods resolution when it comes to generic parameters and inheritance?

1
  • Apparently once you combine Inheritance and Overriding, function resolution by the Compiler becomes wierd: stackoverflow.com/a/48735728/3346583 I asume the resolution for Interfaces works similar to Inheritance (it is a fix for not allowing Multiple Inheritance after all). Commented Feb 13, 2018 at 13:55

2 Answers 2

5

This is specified in the C# spec.


In the first case, we have two candidates:

1. public static void OverloadedFunction(T (= U) a)
2. public static void OverloadedFunction(U a)

§ 7.5.3.6 of the spec says (emphasis mine):

While signatures as declared must be unique, it is possible that substitution of type arguments results in identical signatures. In such cases, the tie-breaking rules of overload resolution above will pick the most specific member.

And the tie-breaking rules (§ 7.5.3.2) say:

A type parameter is less specific than a non-type parameter

T is a type parameter; U isn't. Thus, U is more specific and overload 2 is chosen.


In the second case, we have the following two candidates:

1. public static void OverloadedFunction(T (= V) a)
2. public static void OverloadedFunction(U a)

Here, overload 1 T (= V) is a better match:

  • According to § 7.5.3.3, an identity conversion (V to V ) is a better conversion than any other kind of conversion (such as widening V to U).
  • Thus, according to § 7.5.3.2, overload 1 is a "better function member" than overload 2 for this invocation.
Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for clarification. For information my code breaks getmethod in reflection, saying ambiguous match exception:)
0

It makes sense to me.

It will pick the best match, preferring the overload where no casting is needed. Since you're using Class<V>, then this method:

public static void OverloadedFunction(T a)

effectively becomes:

public static void OverloadedFunction(V a)

which is a better match when accepting a parameter of type V, since no casting is needed.

Your first example is more unpredictable in my opinion, since either one could work. But it does seem like it prefers the strongly-typed method over the generic, which I guess makes sense too.

Reading the specifications, it does look like non-generic methods are preferred: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/expressions#method-invocations

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.