C # overload resolution between generic and nonequivalent methods

I did a basic search on the Internet and stackoverflow, and I saw quite a bit of discussion about overload resolution when both the general version method and the non-general version method are involved. I understand that overload reloading is done at compile time, so 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 "InternalDoStuff" was parsed by the compiler and that the compiler sees "InternalDoStuff is called with a T-type parameter in DoStuff".

However, I do not know if this will matter:

 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 the compiler has enough information to decide that β€œB is a specific version of A”, so they call a non-generic version of InternalDoStuff?

Is there a general principle for analyzing this overload resolution?

+4
source share
4 answers

The second approach is in no sense different from the first approach.

Deriving class B from A will in no way alter the IL code generated for class A. B simply inherits these methods.

If you look at the IL code for class A, you can see that it is compiled to call the generic version instead of the 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 an article by John Skeet here -

As a reminder, overloading is what happens when you have two methods with the same name but different signatures. At compile time, the compiler designs the one that it will invoke, based on the compile time types of the arguments and the purpose of the method call. (I assume you are not using dynamic here, which complicates things a bit.)

As he mentioned, using dynamic delay resolution until runtime. This piece of code will not be a generic option for both of your approaches -

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

For more details see the answers here Jon Skeet and Eric Lippert .

+3
source

In C ++, every type that a program can create at runtime must be generated at compile time. While C ++ templates are similar to C # generators, their behavior is more akin to lookup macros. Since the compiler generates each class separately, which can arise as a result of general type substitutions, it can evaluate such things as overload resolution separately for each of them. C # generics do not work like that.

C # code compilation is divided into two phases. The first step is performed during assembly. The compiler that processes this phase takes the source code and converts it to the "Common Intermediate Language" form (the same CIL form is used for VB.NET, F #, etc. - this is where the name comes from). Each common class definition (for example, List<T> ) in the source code creates one class definition in the form of CIL. The compiler makes all decisions about which overload functions will be used before generating the CIL.

Later, when the program starts, the Common Language Runtime will not generate code for all classes that the program can use, but instead will delay code generation for each class until its first use. During this step, something like List<int> will generate different machine codes from List<string> or List<KeyValuePair<Dictionary<int,string>, Tuple<Cat,Dog>>> . The set of possible types that the program wants to use does not have to be limited. In C #, one could legitimately have a method that, taking into account the generic T parameter, would call a generic method with List<T> (and if the given List<T> passed List<List<T>> , and if given, then it would pass a List<List<List<T>>> etc.). A program is likely to die with an OutOfMemoryException or similar problem if things were nested too deep, but unlike C ++, the number of types that the program could generate does not have to be limited at compile time; only if the program tries to use too many different types will there be a problem.

The CLR is able to create some types of common substitutions when generating code, but it does not handle overload resolution (as already mentioned, it is processed at the stage of translating C # to CIL). While there may be some benefits to the CLR, such as overload resolution, it will also make the CLR much more complex. If a particularly complex problem with overloading takes a quarter of a second, it may not be a problem with compiling C # code, but stopping execution for a quarter of a second for such things would be undesirable.

+2
source

enter image description here This will invoke the "Not General" 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); } } 
+1
source

The call to InternalDoStuff inside DoStuff bound at compile time A<T> . The fact that the call comes from instance B no effect on overload resolution.

At the time of compiling the DoStuff point, DoStuff are 2 members of InternalDoStuff to choose from

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

The DoStuff method passes the value of T , so overloading with int cannot work. Therefore, there is only one applicable InternalDoStuff(T) element, and the compiler selects this one.

+1
source

Source: https://habr.com/ru/post/1498036/


All Articles