Suppose there is such a class.

public class SomeClass<T> { public void DoSomething(int x) { Console.WriteLine("DoSomething(int x) called"); } public void DoSomething(T x) { Console.WriteLine("DoSomething(T x) called"); } } 

And also here is such a code snippet

 var someObj = new SomeClass<int>(); 

I have two questions.

1) The SomeClass<int> private type SomeClass<int> two methods with the same signature. Then why the compiler does not swear at them?

2) Let's say if you call

 someObj.DoSomething(0); 

This code snippet writes "DoSomething (int x) called". Why was the variant with an integer parameter, and not with a generic parameter, called?

  • five
    What plan do you want to see the answer? Because it is written in the language specification (item ...) - will it work? Or do you want to know why the specification is written that way and not otherwise? PS You can call a method with a generic parameter as follows: public static class SomeClassExtension { public static void DoSomethingGeneric<T>(this SomeClass<T> obj, T x) => obj.DoSomething(x); } public static class SomeClassExtension { public static void DoSomethingGeneric<T>(this SomeClass<T> obj, T x) => obj.DoSomething(x); } , someObj.DoSomethingGeneric(0) . - PetSerAl
  • @PetSerAl Why such a tone? I, for example, is also interested. The question is not only how to cause, but also what caused this behavior. If you know an item from a language specification, then tell me. - Igor
  • one
  • 2
  • 2

1 answer 1

Excerpt from the specification (clause 7.5.3.2):

...
In the case of the parameter type sequences {P1, P2, ..., PN} and {Q1, Q2, ..., QN} are equivalent, the following tie-breaking rules are applied, in order to determine the better function member.

• If MP is a non-generic method and MQ is a generic method, then MP is better than MQ.
...

in Russian it sounds like this:

If the parameter sequences are equivalent, the following rules for choosing the best method apply:

  • If the Mp method is not generalized and the Mq method is generalized, then Mp is better than Mq.

There are still many rules for dropouts, but for this case the other rules are irrelevant.

Ok, from where the legs grow they found out, this behavior is written in the specification. How does this look in implementation? Let's take a look at the compiled code and compare the generalized and non-generalized methods. I use ILSpy, but you can use any familiar tool, for example ILDasm.

 // Methods .method public hidebysig instance void DoSomething ( int32 x ) cil managed { // Method begins at RVA 0x205a // Code size 11 (0xb) .maxstack 8 IL_0000: ldstr "DoSomething(int x) called" IL_0005: call void [mscorlib]System.Console::WriteLine(string) IL_000a: ret } // end of method SomeClass`1::DoSomething .method public hidebysig instance void DoSomething ( !T x ) cil managed { // Method begins at RVA 0x2066 // Code size 11 (0xb) .maxstack 8 IL_0000: ldstr "DoSomething(T x) called" IL_0005: call void [mscorlib]System.Console::WriteLine(string) IL_000a: ret } // end of method SomeClass`1::DoSomething 

As you can see, the code of the generalized method of the class remained generalized after compilation (note the ! before the type name in the second method).

The question why the choice is so, and not vice versa answered. Now a little about the possible reasons for such an implementation and selection rules. Possible, because I am not a developer of this specification, but some conclusions can be drawn from general logic.

  1. In generalized methods, you are limited in applying language tools to generalized type variables. For example, you cannot use comparison operators, except for equality and non-equality tests, since we cannot guarantee that they are implemented in the type for which the generic method will be called. Accordingly, if there is a suitable non-generic method, it is likely that it can be implemented more efficiently by using specialized tools of a particular type.

  2. Before calling the generic method, the compiler must replace the generic types with real ones. These actions are not required when using non-generic methods, which means even in trivial cases where the effectiveness of the methods is the same, the cost of calling the generic method is insignificant, but higher.

It turns out that the generalized methods lose performance a little, but what about replacements? After all, we have a common root - the object type, to which any other type can also be brought. I see it like this:

  1. When using generic methods, casting is not used as in the case of using object , and this is a gain in speed, especially for ValueType types, since there is no boxing.

  2. When using generic types, you can be sure that if two variables have the same generic type. then, when executed, their types will also be the same, unlike object variables, within which anything may lie, and additional checks are required to eliminate runtime errors.

  3. When using generic types, we can use clarifications or restrictions, as you like, and explicitly indicate: class or structure, the presence of a constructor (currently only the constructor), the base type, and the list of interfaces that should be implemented in the types which are allowed to be used in a particular generic method or class. This option, like the previous one, for the object type can only be implemented by additional checks and an increase in the amount of necessary code.

Naturally, I could be mistaken about the true reasons for this particular implementation. Naturally, the lists of advantages and disadvantages are far from complete. But I think that this is enough for a general understanding of why generic types and everything associated with them are what they are and their intended use and benefit.


PS: Everything, except the quote from the specification and the IL-code, is my personal opinion, if you disagree with it, then we can discuss it in the chat or you can write your own answer to this question and state your point of view.

Special thanks to @Vadim Ovchinnikov for criticizing the previous version.

  • Honestly, I do not understand where the answer is why. Your answer looks like this: the first part of the answer is a la "so compiled" (which, in principle, is clear to us without IL), the second is the justification for the compiler, and for me it is not clear. And if it were called the other way around, how would this prevent us from “creating generalized extension methods, in particular LiNQ” ? - Vadim Ovchinnikov
  • "The next logical question is: why then specify the exact type at all when using generalized methods, if they remain generalized anyway? To be able to control at the compilation stage, and not throw exceptions at runtime." Do not understand the argument. - Vadim Ovchinnikov
  • @VadimOvchinnikov to the question why, the answer is just in the IL code, there are two signatures in the class method table, one with a specific type, the other with generalized, which is generated dynamically, for the overloaded method, the full match of the signature with the call is first sought, and only then that can theoretically fit, i.e. in this case generalized. And why was it so compiled - to the specification - rdorn
  • @VadimOvchinnikov later I will correct controversial wording to read unequivocally, indeed some things were not written well, but the point is that the same code with the same signature is always used for generalized methods, regardless of the values ​​of the parameter-type - rdorn
  • IL-code is not the reason, it is a consequence of the intentions of the compiler developers. This is if the question "why the wind blows?", I would answer "because the trees are staggering." And the question “why” requires an explanation of the reason . It must be either a specification or statements from the developers of the .NET platform (for example, in blogs), or any compelling reasons for this approach. - Vadim Ovchinnikov