Generics In Depth
Course 1 closed on a preview of LINQ. Course 2 opens on the mechanism underneath nearly everything else in C#'s type system — and the place where C# and Java's shared architecture diverges most sharply of all.
Generic Classes & Methods — Recap
Same surface syntax as java2-1's own generics — a type parameter T, filled in at each use site.
Reified Generics — Real Runtime Type Preservation
Here's the central reversal: java2-1 established that Java erases generic type information after compile-time checking, leaving List<String> and List<Integer> as the exact same runtime class. C# does the opposite — the CLR keeps real, distinct runtime type information for each type argument. List<string> and List<int> are genuinely different types at runtime, not just at compile time.
What Reification Actually Buys You
This resolves java2-1's own "consequences of erasure" list one by one: new T() works (given a constraint, covered next), typeof(T) works directly, and generic arrays of T can be created without the reflection workarounds Java's own erased generics require.
Generic Constraints
Similar spirit to java2-1's own bounded type parameters — but with more constraint kinds available. The class and struct constraints have no Java equivalent at all, precisely because Java has no reference/value type split to constrain against in the first place, unlike csharp1-2's own struct/class distinction.
Performance Implications of Reification
Here's the real payoff: List<int> in C# stores real int values directly, with zero boxing overhead — genuinely comparable to cpp2-1's own C++ templates, not to java1-8's forced-boxing List<Integer>. This directly resolves the exact gap java1-8 named as a genuine cost of Java's erased generics — a real, measurable memory and performance difference for value-heavy collections.
The Real Cost — Code Bloat, Selectively
Reification isn't free, and it isn't identical to C++'s own approach either — worth stating honestly rather than glossing over. For value types, the CLR genuinely specializes a distinct compiled implementation per type, the same class of cost cpp2-1's templates pay for every instantiation. But for reference types, the CLR is smarter: since every reference is the same pointer size underneath, the JIT can share one compiled implementation across all reference-type instantiations — List<Cat> and List<Dog> genuinely share compiled code, unlike List<int> and List<double>. This is a real, distinct middle ground between C++'s full monomorphization and Java's full erasure, not simply "closer to one or the other."
| Aspect | C++ templates (cpp2-1) | Java (java2-1) | C# |
|---|---|---|---|
| Strategy | full monomorphization | type erasure | reified — selective specialization |
| Value-type instantiations | separate compiled code, always | n/a — everything boxes | separate compiled code, like C++ |
| Reference-type instantiations | separate compiled code, always | one shared implementation | one shared implementation, like Java |
| Runtime type info for T | n/a — resolved at compile time | none — erased | real — typeof(T) works |
where T : new() lets a generic method construct T directly — exactly the capability java2-1's own Challenge 2 demonstrated as flatly impossible in Java's erased generics.
List<Cat> and List<Dog> produce two fully separate JIT'd implementations the way List<int> and List<double> do — reference-type generic instantiations genuinely share compiled code under the hood. Reification's real benefit for reference types is preserved runtime type information, not full C++-style code duplication.
Coding Challenges
Create a List<string> and a List<int>, print whether their GetType() values are equal, and explain the result in a comment in terms of reification — contrasting it directly with java2-1's own erasure example.
📄 View solutionWrite a generic method CreateDefault<T>() constrained with where T : new() that returns new T(), and call it successfully with two different types that each have a public parameterless constructor.
📄 View solutionWrite a short paragraph, as a comment, explaining why List<Cat> and List<Dog> can share one compiled implementation under the hood while List<int> and List<double> cannot, tying your answer to the pointer-size argument from this chapter.
📄 View solutionChapter 1 Quick Reference
- C# generics are reified — real runtime type info preserved, the exact opposite of java2-1's own Java type erasure
- new T() and typeof(T) both work directly in C# — impossible under Java's erasure without workarounds
- Generic constraints include class/struct (no Java equivalent) alongside bounded constraints like java2-1's own
- Value-type generic instantiations get separate compiled code (like C++); reference-type instantiations share one implementation (like Java) — a genuine middle ground, not simply "closer to one side"
- List<int> stores real ints with zero boxing, resolving java1-8's own forced-boxing gap
- Next chapter: LINQ in depth — deferred execution, query syntax vs. method syntax, and IEnumerable<T> vs. IQueryable<T>