문제

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?

도움이 되었습니까?

해결책

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.

다른 팁

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.

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

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.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top