Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

c# overload resolution between generic and non-generic methods

I did some basic search on internet and stackoverflow and I saw quite a lot discussion around overload resolution when both generic version method and non-generic version method are involved. I understand that the overload reslolution is done in compile time - therefore if I have this code:

public class A<T>
{
    public void DoStuff(T value)
    {
         InternalDoStuff(value);
    }

    protected void InternalDoStuff(int value)
    {
         Console.WriteLine("Non-generic version");
    }

    protected void InternalDoStuff(T value)
    {
         Console.WriteLine("Generic version");
    }

}

public class Test
{
    static void Main (string [] args)
    {
         A<int> a = new A<int> ();
         a.DoStuff(100);
    }
}

The output will be "Generic version" because the resolution of "InternalDoStuff" has been sorted out by compiler and what compiler sees is "InternalDoStuff is called with a T type parameter in DoStuff".

However I dont know if this will make any difference:

public class B : A <int> 
{

}

public class Test
{
    static void Main (string [] args)
    {
         B b = new B ();
         b.DoStuff(100);
    }
}

Now can I say that compiler has enough information to decide "B is a specific version of A", therefore invoke the non-generic version of InternalDoStuff?

Is there any general principle to analyze these kind of overload resolution?

like image 475
Xinchao Avatar asked Aug 20 '13 15:08

Xinchao


2 Answers

Second approach is not different in any sense to first approach.

Deriving class B from A in no way will change the IL code generated for class A. B is simply inheriting those methods.

If you look at the IL code for class A, you can see that it compiled to call generic version instead of non-generic version -

.method public hidebysig instance void DoStuff(!T 'value') cil managed
{
    .maxstack 8
    L_0000: nop 
    L_0001: ldarg.0 
    L_0002: ldarg.1 
    L_0003: call instance void
            ConsoleApplication1.A`1<!T>::InternalDoStuff(!0) <-- Generic version
    L_0008: nop 
    L_0009: ret 
}

From Jon Skeet's article here -

Just as a reminder, overloading is what happens when you have two methods with the same name but different signatures. At compile time, the compiler works out which one it's going to call, based on the compile time types of the arguments and the target of the method call. (I'm assuming you're not using dynamic here, which complicates things somewhat.)

As he mentioned using dynamic defer resolution till runtime. This piece of code will call non-generic version method for both of your approaches -

public void DoStuff(T value)
{
   dynamic dynamicValue = value;
   InternalDoStuff(dynamicValue);
} 

Refer to the answers here by Jon Skeet and Eric Lippert describing in detail.

like image 71
Rohit Vats Avatar answered Sep 30 '22 01:09

Rohit Vats


In C++, every type which a program could possibly create during execution must be generated at compile time. While C++ templates look like C# generics, their behavior is more akin to substitution macros. Because the compiler generates separately every class that could result from generic type substitutions, it can evaluate things like overload resolution separately for each one. C# generics don't work like that.

The compilation of C# code is divided into two phases. The first phase is done at build time. The compiler that processes that phase takes the source code and converts it into a "Common Intermediate Language" form (the same CIL form is used for VB.NET, F#, etc.--hence the name). Every generic class definition (e.g. List<T>) in source code produces one class definition in CIL form. The compiler makes all decisions about what function overloads will be applied where before generating the CIL.

Later on, when the program is run, the Common Language Runtime will not generate code for all the classes that a program might conceivably use, but will instead defer the generation of code for each class until the first time it's actually used. During this step, something like a List<int> will generate different machine code from a List<string> or a List<KeyValuePair<Dictionary<int,string>, Tuple<Cat,Dog>>>. The set of possible types that a program wants to use need not be bounded. One could in C# legitimately have a method which, given an parameter of generic T, would call a generic method with a List<T> (and if given a List<T> would pass a List<List<T>>, and if given that would pass a List<List<List<T>>> etc.). The program would likely die with an OutOfMemoryException or similar problem if things were nested too deeply, but unlike C++ the number of types a program could generate need not be bounded at compile time; only if the program tries to actually use too many different types would there be a problem.

The CLR is capable of making some types of generic substitutions when generating code, but it doesn't process overload resolution (as mentioned, that's processed in the C# to CIL translation step). While there may be some advantages to having things like overload resolution in the CLR, it would also make the CLR much more complicated. If a particularly tricky overload-resolution problem takes a quarter second, that might not be a problem with compiling C# code, but stopping the runtime for a quarter second for such things would be undesirable.

like image 36
supercat Avatar answered Sep 30 '22 00:09

supercat