Chapter 02 Csharp 2 Generics
3 min read- Chapter 02 — C# 2 (Generics, nullables, delegates, iterators)
- 2.1 Generics
- 2.1.1 Collections before generics (why they mattered)
- 2.1.2 Generics save the day (what you should be able to explain)
- 2.1.3 What can be generic?
- 2.1.4 Type inference for method type arguments
- 2.1.5 Type constraints (encoding “allowed shapes”)
- 2.1.6 `default(T)` and `typeof(T)`
- 2.1.7 Generic type initialization and state
- 2.2 Nullable value types
- 2.2.1 Aim: expressing “no value”
- 2.2.2 CLR and framework support: `Nullable
` - 2.2.3 Language support
- 2.3 Simplified delegate creation
- 2.3.2 Anonymous methods
- 2.3.3 Delegate compatibility
- 2.4 Iterators (`yield`)
- 2.5 Minor features (still interview-relevant)
Chapter 02 — C# 2 (Generics, nullables, delegates, iterators)
Purpose of this chapter: understand the C# 2 features that made “modern C#” possible: generics, Nullable<T>, better delegate syntax (anonymous methods), and iterators (yield).
2.1 Generics
2.1.1 Collections before generics (why they mattered)
The pre-generics world commonly used object-based APIs:
- You lose compile-time safety (casts everywhere).
- You can pay runtime costs (boxing/unboxing for value types).
- Errors show up late (invalid casts) and are harder to refactor safely.
Generics fix this by letting you keep strong typing across APIs.
2.1.2 Generics save the day (what you should be able to explain)
Interview-ready framing:
- Generics let you write one algorithm/data structure that works for many types without sacrificing type safety.
- They help the compiler enforce contracts (e.g., “this collection contains
Customeronly”).
2.1.3 What can be generic?
Common generic targets:
- Classes/structs (e.g.,
List<T>,Dictionary<TKey, TValue>) - Interfaces (e.g.,
IEnumerable<T>) - Methods (e.g.,
T Parse<T>(string input)) - Delegates (e.g.,
Func<T>,Action<T>)
2.1.4 Type inference for method type arguments
Method type inference reduces ceremony:
- You often don’t need to spell out
<T>if it can be inferred from arguments.
Interview nuance:
- Inference can fail with overload resolution ambiguity; know when to make the type explicit.
2.1.5 Type constraints (encoding “allowed shapes”)
Constraints let you express requirements like:
where T : class(reference type)where T : struct(non-nullable value type)where T : new()(public parameterless ctor)where T : SomeBase/where T : ISomeInterface
Why interviewers care:
- Constraints are a way to push correctness into compile time.
- They also affect what you can do inside the generic code (
new T(), calling members, etc.).
2.1.6 default(T) and typeof(T)
default(T):
- Returns the “zero” value for the type parameter.
- For reference types:
null - For non-nullable structs: all fields defaulted
typeof(T):
- Gives you the runtime
Typefor the generic parameter. - Useful for diagnostics, caching per-type, reflection-based behavior (with caution).
2.1.7 Generic type initialization and state
Things to keep clear:
- “Generic static” state is typically per closed constructed type (e.g.,
Cache<int>vsCache<string>). - This can be powerful (type-specific caches) but can surprise you (memory growth across many closed types).
2.2 Nullable value types
2.2.1 Aim: expressing “no value”
Use nullable value types when:
- You need “missing/unknown” but the domain type is a value type (e.g.,
int?,DateTime?).
2.2.2 CLR and framework support: Nullable<T>
Key idea:
T?is syntax forNullable<T>(whenTis a non-nullable value type).
Interview nuance:
- Nullables change operator behavior (“lifting”) and can complicate equality and comparisons if you don’t normalize values.
2.2.3 Language support
Patterns to be fluent with:
x.HasValue,x.Valuex ?? fallbackx?.Member(later versions, but conceptually related: propagate null safely)
2.3 Simplified delegate creation
Delegates let you pass behavior:
- “method group” conversions remove ceremony when the target delegate type is known.
2.3.2 Anonymous methods
Anonymous methods are an early step toward lambdas:
- Create inline delegates without a named method.
- Enable capturing local variables (closures), which is powerful but can produce subtle bugs if you don’t understand capture semantics.
2.3.3 Delegate compatibility
Compatibility rules enable more assignments without manual wrappers, but can confuse overload resolution.
2.4 Iterators (yield)
Iterators let you implement IEnumerable<T> with lazy execution.
Key ideas to rehearse:
- Code runs when enumerated, not when the iterator is created.
yield returnproduces values one-at-a-time.finallyblocks still matter: cleanup should run when enumeration ends (including early exit / exceptions).
Interview traps:
- Multiple enumeration can re-run the iterator body.
- Capturing mutable state in iterators can cause surprising behavior if you reuse the enumerable concurrently.
2.5 Minor features (still interview-relevant)
- Partial types: split a type across files (often used by codegen + hand-written code).
- Static classes: enforce “utility holder” semantics.
- Different access modifiers for getters/setters: control encapsulation without extra backing fields.
- Namespace aliases: disambiguate types with the same name or simplify long namespaces.
- Pragmas / fixed buffers / InternalsVisibleTo: niche but useful to recognize (especially
InternalsVisibleTofor testing).