Nearly every C# surprise reduces to one of two questions. *What got copied?* — because a variable either holds your data or holds a reference to it, and assignment means something different in each case. *When did that run?* — because queries, iterators and awaited methods build machinery now and execute later, somewhere else. The syntax is the easy part; a feature tour will not tell you which half you are looking at. This guide follows those two questions and the third behind them: who cleans up.
Each chapter opens with the short version. Tap one to read the detail.
Value semantics: what a variable holds
~3 min
Every C# type is a value type or a reference type. A value-type variable holds the data, so assignment copies it; a reference-type variable holds a reference, so both names then reach one object.
Two different nulls that share a keyword
~2 min
int? is a real type, Nullable<int>, with runtime behaviour. string? is an annotation the compiler reads and then erases — it changes what you get warned about and nothing else. Confusing the two is the whole difficulty.
Classes: what you expose, and what a subclass really gets
~2 min
A property is a pair of methods, not storage, which is why it can gain validation later without breaking callers. override dispatches on the runtime type; new hides instead, so one object behaves differently through two references.
Pattern matching, and the switch that returns a value
~2 min
Modern C# tests the *shape* of a value, not its type alone. Arms are checked in text order and the compiler errors on an arm an earlier one already covers — but a switch expression that misses a case only warns, then throws at run time.
Arithmetic, and the conversions you did not write
~2 min
Integer division truncates, integer overflow wraps silently unless you ask for checked, and compound assignment quietly inserts a narrowing cast. Floating-point arithmetic never throws — it hands you Infinity or NaN and carries on.
Generics, and which substitutions are allowed
~2 min
A type parameter is checked once and reused for every argument, which removes both casts and boxing. Constraints are what let you *do* anything with a T. Variance decides when Thing<Derived> may stand in for Thing<Base> — by default it may not.
Delegates, lambdas and what a closure captures
~2 min
A delegate is a type describing a signature; an instance points at a method. A lambda captures the *variable*, not its value at that moment, and keeps it alive as long as the delegate lives — which explains a whole class of leak.
Deferred execution: iterators and queries
~2 min
Building a query executes nothing. yield return and the query operators in System.Linq both hand you a description of work; the work happens when something enumerates it, once per enumeration.
async/await is not a thread
~2 min
await does not start a thread. It signs the rest of the method up as a continuation and returns control to the caller. Blocking on the resulting task with .Result or .Wait() is the classic deadlock, and async void throws exceptions nobody can catch.
Exceptions: unwinding, filters, and rethrowing
~2 min
The runtime walks up the call stack for the first matching catch, so clauses are checked top to bottom and the most derived type must come first. throw; preserves the original stack trace; throw e; overwrites it.
Cleanup: the collector, IDisposable, and spans
~2 min
The collector frees memory by reachability from roots, not by counting references — so cycles are collected, and anything a static field or a live delegate can reach never is. It knows nothing about file handles, which is what IDisposable is for.
Written by Keentune. We are not affiliated with or endorsed by the organizations whose documentation informs this guide, and any linked sources belong to their respective owners.
All exam, test, and product names and trademarks are the property of their respective owners and are used here for identification and reference only. Keentune is independent study practice — not affiliated with, authorized, or endorsed by any of these organizations.