You can't provide provide specializations for generics in C# and that often leads to performance penalty (for example you can't provide a specialization that would avoid boxing).
Yes, that's exactly the point I was making. C++ templates sit in this odd place between a macro system and a generics system--generics are the least powerful and most restrictive, macros are the most powerful and least restrictive, and C++ sits in the middle but its usability and ergonomics are exceptionally poor compared to either option.
However, in practice you often avoid boxing and unboxing in C#. This is due to the differences between the type system for C# and Java, with Java generics are built with type erasure which forces you to box and unbox everything. With C#, internally a List<T> will have a T[] inside it, and if T is a struct type you will not need to box or unbox it.
I'm not sure what examples you are thinking of where you are paying for boxing and unboxing due to generics in C#, I guess I haven't run into that particular problem. In general, generics in C# avoid boxing and unboxing. That's the difference between System.Collections (which doesn't use generics, and requires boxing/unboxing) and System.Collections.Generic (which does use generics, and as a direct consequence, avoids boxing/unboxing).
You can't provide a single generic method that works with T for both value types and reference types, but works without boxing/unboxing for value types. With specialization, you would be able to specialize the relevant part depending on the type.
Either that's factually incorrect or we have a disagreement about what "specialization" means.
In my mind, "specialization" means ad-hoc polymorphism. That is, you can change the behavior of a generic function, depending on its instantiated types. C# does not have this.
However, that does not mean that you get the same machine code for both object and reference types. As an optimization, when you use a template in C#, code is generated for the instance you are actually using. For value types, this means that the code will not box or unbox. This is clearly not specialization, at least by the definition I'm using, but it also avoids unboxing.
> The interesting question is how does .NET compile the generic IL of the server to machine code. It turns out that the actual machine code produced depends on whether the specified types are value or reference type. If the client specifies a value type, then the JIT compiler replaces the generic type parameters in the IL with the specific value type, and compiles it to native code. However, the JIT compiler keeps track of type-specific server code it already generated. If the JIT compiler is asked to compile the generic server with a value type it has already compiled to machine code, it simply returns a reference to that server code. Because the JIT compiler uses the same value-type-specific server code in all further encounters, there is no code bloating.
> Because the generic code does not force the boxing and unboxing of value types, or the down casting of reference types, performance is greatly improved.
However, in practice you often avoid boxing and unboxing in C#. This is due to the differences between the type system for C# and Java, with Java generics are built with type erasure which forces you to box and unbox everything. With C#, internally a List<T> will have a T[] inside it, and if T is a struct type you will not need to box or unbox it.
I'm not sure what examples you are thinking of where you are paying for boxing and unboxing due to generics in C#, I guess I haven't run into that particular problem. In general, generics in C# avoid boxing and unboxing. That's the difference between System.Collections (which doesn't use generics, and requires boxing/unboxing) and System.Collections.Generic (which does use generics, and as a direct consequence, avoids boxing/unboxing).