Type Erasure vs Reified Generics
Java generics are primarily implemented using type erasure: generic type arguments are mostly removed from the runtime representation, with appropriate casts inserted by the compiler and bridge methods where necessary. Consequently, code generally cannot directly ask for the generic type argument at runtime, and Java cannot use primitive types such as int directly as generic type arguments. C# generics are reified: generic type information remains available at runtime, and the runtime can distinguish constructed generic types. C# also supports generic value types without boxing in many cases.
Java generic type arguments are primarily erased at runtime.
Java reflection can see generic metadata in some declarations, but that does not make generic arguments fully reified in ordinary runtime type checks.
C# generic type arguments are reified at runtime.
C# supports generic value types such as List without requiring the same boxing approach used by object-based collections.
Type erasure helps Java maintain compatibility with much older bytecode and libraries.
Reification enables richer runtime generic operations.