Generics
Generic types and methods, constraints, IEnumerable vs IQueryable, and generic collections.
7 questions
JuniorTheoryVery commonWhat are generics in C#, and what problem do they solve over using object?
What are generics in C#, and what problem do they solve over using object?
Generics parameterize a type or method by a type argument T, so one definition like List<T> works for any type while staying strongly typed. This gives compile-time type safety and code reuse without object casts or boxing of value types. The JIT specializes the code per value type, keeping it efficient at runtime.
Common mistakes
- ✗Thinking generics are just
objectcontainers with nicer syntax, missing the compile-time type checking - ✗Assuming C# erases generic types at runtime like Java, when
Tstays reified and recoverable - ✗Believing
List<int>boxes its elements, when JIT specialization keeps value types unboxed
Follow-up questions
- →How does CLR generic specialization differ for reference types versus value types?
- →Why can you recover
Tvia reflection in C# but not in Java's erased generics?
JuniorTheoryCommonWhat do List<T>, Dictionary<TKey,TValue> and HashSet<T> give over ArrayList?
What do List<T>, Dictionary<TKey,TValue> and HashSet<T> give over ArrayList?
List<T> is a growable array, Dictionary<TKey,TValue> a hash-keyed map, and HashSet<T> a unique-element set. Being generic, they store one known element type, so the compiler enforces type safety and value-type elements avoid boxing. The old ArrayList/Hashtable held object, forcing casts and boxing every value.
Common mistakes
- ✗Believing generic collections are mere syntax sugar over
ArrayList, missing their compile-time type checking - ✗Thinking storing
intinList<int>still boxes, when generics keep value-type elements unboxed - ✗Assuming you must cast on read from
Dictionary<TKey,TValue>, as you did withHashtable
Follow-up questions
- →When would you reach for
SortedDictionary<TKey,TValue>instead ofDictionary<TKey,TValue>? - →How does
HashSet<T>decide whether two elements are equal?
JuniorTheoryCommonWhat contract does IEnumerable<T> define, and how does foreach use it?
What contract does IEnumerable<T> define, and how does foreach use it?
IEnumerable<T> exposes a single GetEnumerator() returning an enumerator with MoveNext() and Current. foreach calls GetEnumerator(), then loops MoveNext() reading Current each step. It models forward-only, read-only iteration and is the basis for lazy, deferred sequences that produce elements on demand.
Common mistakes
- ✗Assuming
IEnumerable<T>guarantees aCountor indexer, when it only promises sequential forward access - ✗Thinking the whole sequence is built up front, missing that elements can be produced lazily on demand
- ✗Confusing the enumerator's
Current/MoveNext()with members on the collection itself
Follow-up questions
- →How does a
yield returnmethod implement this contract without a hand-written enumerator? - →Why can iterating the same
IEnumerable<T>query twice re-run its underlying work?
MiddleTheoryCommonWhat do where T: generic constraints do, and what kinds can you apply?
What do where T: generic constraints do, and what kinds can you apply?
A where T: clause restricts which type arguments are allowed so the method body can use those guaranteed capabilities. Kinds include class and struct (reference vs value type), new() (a public parameterless constructor), a base class, an interface, and notnull. With where T: IComparable<T> you can call CompareTo on a T value.
Common mistakes
- ✗Thinking constraints are checked at runtime, when the compiler rejects invalid type arguments up front
- ✗Believing only one constraint is allowed per parameter, when several can be combined in one clause
- ✗Forgetting that without a constraint the body can only treat
Tasobject, not call its members
Follow-up questions
- →Why must
new()come last when combined with other constraints on the sameT? - →How does
where T: structdiffer fromwhere T: notnullfor a value type?
MiddleTheoryCommonHow does a generic method differ from a generic type, and how is T usually supplied?
How does a generic method differ from a generic type, and how is T usually supplied?
A generic method declares its own type parameters in angle brackets after its name, independent of any type parameters on the containing class — even a non-generic class can host one. The compiler usually infers T from the argument types at the call site, so you rarely write <T> explicitly; you only specify it when inference cannot determine it.
Common mistakes
- ✗Thinking only a generic class can declare a generic method, when a non-generic class can host one too
- ✗Always spelling out
<T>at the call site, unaware the compiler infers it from the arguments - ✗Confusing the method's own type parameters with the enclosing type's parameters
Follow-up questions
- →Why can't the compiler infer
Tfrom a generic method's return type alone? - →How does overload resolution interact with generic type argument inference?
MiddleTheoryOccasionalWhat value does default(T) produce, and why is it needed for an open T?
What value does default(T) produce, and why is it needed for an open T?
default(T) (or just default) yields the type's zero value: null for reference types, 0/false for numeric and bool value types, and an all-zeroed instance for a struct. It is needed because inside a generic you cannot write a literal that fits every possible T, yet you still need a valid initial or fallback value for that type.
Common mistakes
- ✗Assuming
default(T)is alwaysnull, forgetting it is0/false/a zeroed struct for value types - ✗Thinking
default(T)calls a constructor or runs field initializers, when it just zeroes the memory - ✗Believing you can write a literal like
0ornullfor an openTinstead of usingdefault
Follow-up questions
- →How does
defaultfor a nullable value typeint?differ fromdefault(int)? - →Why does the target-typed
defaultform let you omit theTin many contexts?
SeniorTheoryOccasionalHow does IEnumerable<T> differ from the queryable interface IQueryable<T> in LINQ execution?
How does IEnumerable<T> differ from the queryable interface IQueryable<T> in LINQ execution?
IEnumerable<T> runs LINQ in memory: its operators are compiled delegates iterating objects locally (LINQ-to-Objects). IQueryable<T> instead builds an expression tree that a provider translates — for example to SQL — and executes at the source, so filtering and paging are pushed remotely. Calling an IEnumerable<T> operator on a DB query first pulls every row into memory.
Common mistakes
- ✗Swapping which interface translates to SQL, attributing the expression tree to
IEnumerable<T> - ✗Calling an
IEnumerable<T>operator early, accidentally pulling the whole table into memory before filtering - ✗Assuming both defer identically, missing that only
IQueryable<T>pushes work to the data source
Follow-up questions
- →What happens when an
IQueryable<T>provider cannot translate a custom method in the expression tree? - →How does the queryable interface
IQueryable<T>decide where the in-memory boundary falls in a mixed query?