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.
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.
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:
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.
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.
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.
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