5

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?

1
  • 3
    Your first question -- "can I say that the compiler has enough information to call the non-generic version?" -- can be answered by trying it yourself. Your second question can be answered by reading the overload resolution section of the C# specification. Commented Aug 20, 2013 at 17:26

4 Answers 4

4

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.

Sign up to request clarification or add additional context in comments.

1 Comment

"Deriving class B from A in no way will change the IL code generated for class A. B is simply inheriting those methods." -- this is a convincing point
2

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.

6 Comments

I'm not sure you do justice to C++ templates by explaining them in terms of C# generics or macros. Also consider that C++ templates pre-existed C# generics by quite a long time, so it's traditional to explain C# generics in terms of templates and the differences (but the converse may be more suitable for an audience that understands generics well; however this question wouldn't need to be asked by such an audience)
@BenVoigt: C++ templates operate at a semantic level substantially beyond preprocessor macros, but my point is that if one defines a template Foo<T>, and defines variables of type Foo<Larry>, Foo<Curly>, and Foo<Moe>, the C++ compiler will turn that into three non-generic classes. By contrast, if Foo<T> were a C# generic class, the C# compiler would output one piece of CIL code which would specify a generic class.
Yes I agree. The C++ compiler performs instantiation of templates, and the C# compiler doesn't instantiate generics (this is also the reason specializations aren't a feature of generics), and even at the CLR level, there's no instantiation of generics unless the type arguments are value types, and while instantiation with value types does allow inlining and avoid boxing, it doesn't change overload selection.
So your statement about "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" isn't really accurate either, because "code generation for each class" doesn't actually happen, ever. There's one set of native code for all classes, which relies on virtual dispatch via interface pointers.
@BenVoigt: I don't think the sharing of code between List<Button>.GetEnumerator() and List<Control>.GetEnumerator() particularly affects program semantics, since a delegate constructed to point to one will report a different Method from one constructed for the other. Having some executable code shared may reduce memory consumption, but that's a CLR implementation detail.
|
1

enter image description hereThis will invoke the "Non-generic" version:

public class A<T>
{
    public virtual 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 B : A<int>
{
    public override void DoStuff(int value)
    {
        InternalDoStuff(value);
    }
}

6 Comments

Well, I tried it. Both cases the output is "Generic version". I tried targeting .Net 4.5, 4.0, 3.5. The results are the same. (I am using VS2012)
@Matthew - Above code will print non-generic version. Notice Alex have overriden DoStuff method.
Screenshot of output on my system added (Net 4.5, 64 bit, release build)
ahh, okie. I didnt read your code carefully enough. In your case you overrided DoStuff in class B. Then InternalDoStuff is invoked with a argument of type "int" - this is known at compile time. That's why you got the "non-generic" output
Yeah that's Virtual dispatching where most derived version gets called at runtime.
|
1

The call to InternalDoStuff inside of DoStuff is bound at the time A<T> is compiled. The fact that the call is coming from an instance of B doesn't impact the overload resolution in any way.

At the point DoStuff is compiled there are 2 InternalDoStuff members to choose from

  • InternalDoStuff(T value)
  • InternalDoStuff(int value)

The DoStuff method is passing a T value hence the overload with int can't work. Hence there is only one applicable member InternalDoStuff(T) and the compiler chooses this one.

5 Comments

Thanks, man. But sounds like you are suggesting the same result as Alex - the "Non-generic" version will be called. Am I right? I tried it out and the result is "Generic version". It does not matter which version of .Net framework I am targeting. About your comment "Formerly generic, now fully instantiated", what exactly do you mean by "fully instantiated" method?
@Matthew blah, I misread the original sample. I thought the call on B was to InternalDoStuff
right. If it is InternalDoStuff then the non-generic version will be called. However it probably does not because B makes A "specific or fully initiated". It converges to the problem of the resolution of InternalDoStuff (T value) vs. InternalDoStuff(int value). If you make InternalDoStuff public and call it directly on A<int>, the result will be the same: non-generic version will be called
The thing confuses me is: compiler will complain if you write b.DoStuff("a string"). It gives me the impression that compiler is indeed aware that the T in B is int. When analyzing DoStuff purely in the context of A, the "compile time type" of value is unkown (T). However given the context of B and given the fact DoStuff is called from B, it is clear-even at the compile time-the value passed to DoStuff is of type int. I thought compiler can do some kind of text-replace in the case of B...
@Mathew the thing you have to realize is there are 2 separate points of compilation which are independent of each other and happen a diff number of times: the body of Dostuff and the invocation of DoStuff. The body of DoStuff is compiled once and assumes nothing about T. The invocation of DoStuff is compiled many times and does consider the current value of T if it is resolved. The type of resolution you are looking for is possible in languages like C++. But it works there because the body of DoStuff is compiled multiple times

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.