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?
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:
V
to V
) is a better conversion than any other kind of conversion (such as widening V
to U
). 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://docs.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/expressions#method-invocations
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.