C# Resources
Knowledge
- Docs: "Fundamentals of garbage collection", Microsoft Learn
Official docs for the managed heap's generational design: why it's split into generations 0, 1 and 2, the promotion rule between them, and the generational hypothesis that most objects should die in generation 0. Use for: what actually happens to a class instance after lesson 1 puts it on the heap. - Docs: "Large object heap (LOH) on Windows", Microsoft Learn
Official docs for the size threshold above which an object bypasses the normal generation 0 to 1 to 2 path entirely, and why the LOH is only collected alongside a generation 2 collection. Use for: why a large array or buffer is a different, more expensive kind of allocation. - Docs: "Workstation vs. server garbage collection (GC)", Microsoft Learn
Official docs for the two GC flavors: one collecting thread versus one per logical processor, and the documented case (many processes sharing few CPUs) where server GC's own parallelism backfires. Use for: choosing a GC flavor from a deployment's actual shape. - Docs: "Background garbage collection", Microsoft Learn
Official docs for background (concurrent) GC: why it only ever applies to generation 2, the thread-count difference between workstation and server background GC, and foreground GC as the one case a background collection still stops everything. Use for: what "concurrent" GC actually leaves concurrent. - API: "Span
Struct", Microsoft Learn
Official API reference and remarks forSpan<T>: a stack-only, allocation-free view over contiguous memory, and the full list of ref-struct restrictions (no boxing, no heap fields, no lambda capture, no crossingawait/yield) that guarantee it. Use for: what a ref struct actually forbids, and why. - Docs: "Memory
and Span usage guidelines", Microsoft Learn
Official guidance forMemory<T>asSpan<T>'s heap-safe counterpart, the ownership/consumption model for a buffer, and the rule to prefer the read-only variants when a buffer is only read. Use for: choosing between the four related types, and for the discipline of claiming only the access a method actually needs. - Docs: "stackalloc expression", Microsoft Learn
Official reference for stack allocation: why pairing it withSpan<T>/ReadOnlySpan<T>needs nounsafecontext, the automatic buffer-overrun detection the CLR enables when it's used, and theStackOverflowExceptionrisk from over-allocating. Use for: whatstackallocactually does, and its real failure mode. - API: "ArrayPool
Class", Microsoft Learn
Official API reference for the shared array pool: the rent/return contract, thatRentmay hand back a larger array than requested but never a smaller one, and that there is no finalizer safety net for a forgottenReturn. Use for: the fallback for a buffer too large or too long-lived forstackalloc. - Docs: "How BenchmarkDotNet works", BenchmarkDotNet
Official docs for the harness's internal pipeline: an isolated Release-mode process per benchmark, the Pilot/Warmup/Actual stages that measure and subtract the harness's own overhead, and the delegate-invocation trick that stops the JIT from inlining a benchmark method away. Use for: why a raw stopwatch loop isn't trustworthy and what actually fixes that. - Docs: "Diagnosers", BenchmarkDotNet
Official docs forMemoryDiagnoser: bytes allocated per operation viaGC.GetAllocatedBytesForCurrentThread, 99.5% accuracy at default settings, theGenXcollections-per-1000-operations columns, and that it counts managed heap allocations only. Use for: measuring lesson 1's struct-vs-class allocation claim instead of reasoning about it. - Docs: "Profiling tools in .NET", Microsoft Learn
Official overview of the .NET diagnostic tools, and wheredotnet-countersanddotnet-traceeach fit. Use for: orientation before reaching for a specific tool. - Docs: "dotnet-trace", Microsoft Learn
Official reference for the trace-capture tool: its current default profiles (dotnet-common,dotnet-sampled-thread-time), the dedicatedgc-verbose/gc-collectprofiles, and why the oldercpu-samplingprofile name was removed. Use for: capturing the specific hot stack behind a symptom. - Docs: "dotnet-counters", Microsoft Learn
Official reference for the ad-hoc health-monitoring tool built onEventCounter/Meter. Use for: noticing a symptom cheaply, before a deeper, more expensive trace. - Docs: "Implement a Dispose method", Microsoft Learn
Official reference for the full dispose pattern: the publicDispose()/protectedDispose(bool disposing)split, what thedisposingparameter changes about what's safe to touch, and when a finalizer is (and isn't) worth adding. Use for: implementingIDisposablecorrectly, not just consuming it. - Docs: "Implement a DisposeAsync method", Microsoft Learn
Official reference for the async half:DisposeAsyncCore()for a non-sealed class, whyDisposeAsync()callsDispose(false)rather thanDispose(true), idempotency, and cascading disposal through a chain of owned objects. Use for: implementingIAsyncDisposablealongside (or instead of)IDisposable. - Docs: "Logging in .NET and ASP.NET Core", Microsoft Learn
Official docs forILogger's category, log levels, and providers. Use for: what a logger actually attaches to each entry, and how the minimum emitted level is configuration rather than code. - Docs: "Logging in C#", Microsoft Learn
Official docs for message templates: named placeholders passed separately from their values, kept as queryable structured properties rather than collapsed into a flat interpolated string. Use for: the habit correction that matters most coming from plain string formatting. - Docs: "Health checks in ASP.NET Core", Microsoft Learn
Official docs for the liveness/readiness split,AddHealthChecks/MapHealthChecks, and the documented example of why a slow-starting dependency should fail readiness without failing liveness. Use for: wiring a service so an orchestrator can tell "not ready yet" apart from "actually crashed." - Docs: ".NET Observability with OpenTelemetry", Microsoft Learn
Official docs explaining that .NET'sILogger,Meter, andActivity/ActivitySourceAPIs are the platform's own instrumentation, with OpenTelemetry exporting what they already emit rather than replacing them. Use for: the three pillars of observability, in .NET's own terms. - Docs: "Add distributed tracing instrumentation", Microsoft Learn
Official reference forActivitySource/Activityas .NET's Tracer/Span, and the documented performance optimization whereStartActivity()returnsnullwith no listener registered. Use for: why tracing instrumentation is safe to leave in a hot path. - Docs: "Overview of ASP.NET Core Authentication", Microsoft Learn
Official docs for authentication's job: producing theClaimsPrincipal, and theUseAuthenticationmiddleware-ordering requirement. Use for: what authentication actually establishes, distinct from authorization. - Docs: "Introduction to authorization in ASP.NET Core", Microsoft Learn
Official docs stating authorization as separate and distinct from authentication, and the requirement/handler/policy model. Use for: the core distinction this lesson is built on. - Docs: "Policy-based authorization in ASP.NET Core", Microsoft Learn
Official docs for bundling requirements into a named policy, applied via[Authorize(Policy = "...")]orRequireAuthorization(...). Use for: declaring what an endpoint needs instead of an inline permission check. - Docs: "Claim-based authorization in ASP.NET Core", Microsoft Learn
Official docs for the simplest policy shape: checking a claim's presence, optionally its value. Use for: the base case most simple authorization policies reduce to. - Docs: "Migrate from Newtonsoft.Json to System.Text.Json", Microsoft Learn
Official docs stating the bare serializer's own defaults (unchanged casing, case-sensitive matching) against what ASP.NET Core configures automatically (camelCase, case-insensitive, quoted-number deserialization). Use for: the divergence between testing the serializer directly and testing through the framework. - Docs: "How to use source generation in System.Text.Json", Microsoft Learn
Official docs forJsonSerializerContext,[JsonSourceGenerationOptions], and the gotcha where an explicitJsonSerializerOptionsinstance passed to the generated context's constructor overrides the attribute's settings. Use for: compile-time, reflection-free serialization, and why AOT scenarios need it. - Docs: "Breaking changes in .NET", Microsoft Learn
Official docs categorizing a change's compatibility impact: binary, source, and behavioral, among six documented types overall. Use for: precisely naming what kind of breaking change a modification actually is. - Docs: "How the .NET Runtime and SDK are versioned", Microsoft Learn
Official docs for .NET's semantic-versioning scheme: MAJOR for breaking changes, MINOR for backward-compatible additions, PATCH for fixes, and the high bar for a MAJOR bump. Use for: what a version number is supposed to promise a consumer. - API: "ObsoleteAttribute Class", Microsoft Learn
Official API reference for the warning/error escalation (CS0618/CS0619) and the message argument. Use for: the mechanics of marking a member deprecated. - Docs: "Obsolete features in .NET 5+", Microsoft Learn
Official docs forDiagnosticIdandUrlFormat, the .NET runtime's ownSYSLIB0XXX/EXTOBS0XXXconventions, and why the standard diagnostic ID can't be suppressed one obsoletion at a time. Use for: making one specific deprecation individually suppressible and documented. - Docs: "NuGet Package Version Reference", Microsoft Learn
Official docs for NuGet's SemVer-compliant version format, the requirement that a version be specified at upload, and the documented advice against an upper-bounded dependency range with no known compatibility problem behind it. Use for: setting and consuming a package version correctly. - Docs: "Pre-release versions in NuGet packages", Microsoft Learn
Official docs for pre-release suffixes: that NuGet enforces nothing about their meaning beyond marking a version pre-release, common conventions (-alpha/-beta/-rc), and that stable versions take precedence once the suffix is dropped. Use for: publishing and interpreting a pre-release package version. - Docs: "Types (C# reference)", Microsoft Learn
Official docs on C#'s value-type/reference-type split, the distinction that decides where a struct belongs versus a class. Use for: the type-system foundation everything else in this workspace assumes. - Docs: "Structure types (C# reference)", Microsoft Learn
Official reference for when a struct is the right choice, and what copy semantics and allocation behavior it brings that a class doesn't. Use for: naming precisely where a Java habit (everything is a class) misleads. - Docs: "The lambda operator", Microsoft Learn
The reference for both jobs of=>: the lambda operator, and the separator in an expression body definition. Carries the two rules that decide what a body may be, that a value-returning member needs a result implicitly convertible to its return type, and that avoidmember, constructor, finalizer orset/init/add/removeaccessor needs a statement expression (assignment, invocation, object creation, increment or decrement, orawait) whose result is discarded. Use for: writing a compact member, and for telling the token's two roles apart when reading. Note that the older expression-bodied-members URL now serves this article. - Docs: "Asynchronous programming scenarios", Microsoft Learn
Worked examples of waiting for several tasks, including the LINQ form and the warning that makes it safe: LINQ's deferred execution means that without immediate evaluation the async calls do not happen until the sequence is enumerated, so theToArrayis what starts the work. Carries a separate caution about asynchronous lambdas in LINQ executing at an unexpected time, deadlocking easily if blocking is introduced, and being hard to reason about when nested. Use for: building a task list correctly, and for the argument against a bareSelectreturning tasks. - Docs: "Consuming the Task-based Asynchronous Pattern", Microsoft Learn
The built-in combinators in practice:WhenAll's overloads for uniform and non-uniform task sets and how exceptions propagate out of awaiting it, and theWhenAnyinterleaving loop, which awaitsWhenAny, removes the completed task from the list, then awaits that task for its value. Use for: processing results as they arrive, and for whyWhenAnyneeds two awaits. - Docs: "await operator", Microsoft Learn
The mechanism in one paragraph:awaitsuspends the enclosing async method, does not block the thread, and returns control to the caller when it suspends, while an already-completed operand returns its result without suspending. Also the type ofawait tfor each task type, that a faulted operand has its exception rethrown rather than wrapped, and the three placesawaitis forbidden (a synchronous local function, alockblock, anunsafecontext). Use for: explaining what happens to a method's execution at an await, and for why a lock cannot be held across one. - Docs: "async keyword", Microsoft Learn
The return-type rules for an async method (Task,Task<TResult>,void, or any type with an accessibleGetAwaiter), the parameter restrictions (noin,ref,ref readonlyorout, and no reference return), and the guidance onasync void: avoid it outside event handlers, because callers cannot await such methods and must find another way to observe completion or failure. Use for: choosing a return type, and for the argument againstasync voidin review. - Docs: "Task-based asynchronous programming", Microsoft Learn
TheTasktype itself, before any keywords: a task as an asynchronous operation at a higher level of abstraction than a thread, queued to the thread pool;Statusand theTaskStatusenumeration; creation withTask.RunandTaskFactory.StartNew; tasks without delegates viaTaskCompletionSource<TResult>andFromAsync; the blocking family (Wait,WaitAll,WaitAny,Result) againstContinueWithand the asynchronousWhenAll/WhenAnywithWhenAny's four documented scenarios; and the exception model, where failures are wrapped in anAggregateExceptiondelivered to the joining thread, with the warning that readingExceptionbefore garbage collection is what stops an unobserved failure terminating the process. Use for: what anawaitis actually awaiting. - Docs: "Generate and consume async streams", Microsoft Learn
The tutorial that converts aTask<IEnumerable<T>>paged query into an async stream, so it carries both shapes and the comparison between them. The three interfaces (IAsyncEnumerable<T>,IAsyncEnumerator<T>,IAsyncDisposable) against their synchronous counterparts, withValueTaskused in them for performance reasons;yield returnin a method declaredasync; and the finished-application comparison, where the first page is enumerated as soon as it is available, notry/catchis needed for cancellation because the caller can stop enumerating, progress needs no callback object, and no collection is allocated before enumeration. Also the cancellation section: the same protocol as otherasyncmethods, plus[EnumeratorCancellation], which causes the compiler to generate code making the token passed toGetAsyncEnumeratorvisible to the body of the iterator. Use for: deciding between returning a collection and streaming it, and for the one attribute without which a cancelable-looking stream ignores its consumer. - Docs: "yield statement", Microsoft Learn
Reference foryield returnandyield break, with iteration also finishing when control reaches the end of an iterator, and the restrictions: noyieldin methods within,reforoutparameters, and none in lambda expressions or anonymous methods. Use for: writing an iterator, and for reading the overlap with the async method's own parameter restrictions. - Docs: "Cancellation in Managed Threads", Microsoft Learn
The cancellation model itself: a unified model for cooperative cancellation built on a lightweight token, with the two clauses that define it, that only the requesting object can issue the cancellation request and that each listener is responsible for noticing it and responding appropriately and in a timely manner. Also the four-step pattern aroundCancellationTokenSource, that cancellation refers to operations rather than objects and means stop as soon as possible after any required cleanup, thatIsCancellationRequestedcannot be reset so a token cannot be reused after cancellation, andRegisterwith itsCancellationTokenRegistrationfor listeners that block and cannot poll. Use for: why a token is a message rather than a switch, and for the lifetime of a token source. - Docs: "Task cancellation", Microsoft Learn
How the model lands in the task classes, with the distinction the whole subject turns on: returning from the delegate leaves the task inRanToCompletionrather thanCanceled, while throwingOperationCanceledExceptioncarrying the requested-on token, preferably viaThrowIfCancellationRequested, transitions it toCanceledso the calling code can verify the task responded. Carries the comparison rule behind that, where the task matches the exception's token against the token passed to the API that created it, and the note that aTaskCanceledExceptionfromWaitorWaitAllindicates successful cancellation rather than a fault. Use for: making cancellation observable, and for why the token is passed both into the delegate and toTask.Run. - Docs: "Nullable reference types", Microsoft Learn
The feature explained as declared intent plus compiler warnings:?as an annotation, null-state tracked as not-null or maybe-null, the two things that update a local's state (assignments and null checks), analysis followingif, pattern matching and early returns, and the null-forgiving!with the documentation's own discipline that each occurrence is a place the compiler can no longer protect you. Use for: making required and optional values visible in a signature. - Docs: "Nullable reference types (C# reference)", Microsoft Learn
The reference half, with the statements that settle what the feature is: nullable reference types are annotations on existing reference types rather than new class types,stringandstring?are bothSystem.String, there is no runtime difference and no runtime checking added. Also the Important that other libraries may read the annotations by reflection, Entity Framework Core reading a nullable reference as optional and a non-nullable one as required. Use for: the boundary againstNullable<T>, and for where an annotation does have runtime consequences. - Docs: "Using Delegates", Microsoft Learn
A delegate as a type that safely encapsulates a method, with the two facts that decide how multicast delegates behave: an uncaught exception in one method passes to the caller and no later methods in the invocation list run, and a delegate with a return value oroutparameters yields those of the last method invoked. Also that a delegate's signature includes its return type, unlike an overload's. Use for: reading a delegate-typed member, and for why a multicast delegate with a return value is a design mistake. - Docs: "Handling and raising events", Microsoft Learn
Events as the delegate model following the observer pattern, plus the .NET conventions:EventHandlerandEventHandler<TEventArgs>, event data classes named with anEventArgssuffix, andEventArgs.Emptyfor an event carrying no data. Use for: declaring an event that looks like every other event in the framework. - Docs: "The event keyword", Microsoft Learn
What the keyword actually does: declares a member of a delegate type whose users may add or remove their handlers and nothing else, with triggering invoking all supplied handlers. Use for: the argument against exposing a plain delegate-typed field, which lets any caller overwrite the subscriber list or raise the event itself. - Docs: "Query expression basics (LINQ)", Microsoft Learn
The grammar of a query expression: it must begin withfromand end withselectorgroup, withwhere,orderby,join,letand furtherfromclauses between, andintoto continue a query after a join or grouping. Also that a query expression is a first-class language construct whose variables are all strongly typed. Use for: writing query syntax, and for knowing what cannot be said in it. - Docs: "Write LINQ queries", Microsoft Learn
Query syntax and method syntax side by side over the same data, with identical output and the sameIEnumerable<T>query-variable type, and the observation thatIEnumerable<T>has noWheremethod of its own: the standard query operators are extension members over it. Use for: the link between the extension-method mechanism and every LINQ call site. - Docs: "Extension members (C# Programming Guide)", Microsoft Learn
Official guide to both syntaxes, thethisfirst parameter and the C# 14extensionblock, with the note that they compile to the same IL. Most valuable for the binding section: extension members always have lower priority than the type's own members, an extension with a matching signature is never called, and the compiler binds to the first extension it finds rather than weighing candidates. Use for: predicting where a call actually resolves, and for why an extension cannot change behaviour. - Docs: "Extension declaration (C# Reference)", Microsoft Learn
Reference forextensionblocks: the receiver the block names and scopes across its instance members, that methods, properties, indexers and operators are all declarable, instance against static extensions, multiple blocks per class, generics with constraints, and the unnamed receiver form for static-only blocks. Use for: giving a type you do not own a property or an operator. - Docs: "Constraints on type parameters", Microsoft Learn
The full constraint table with the rules that catch people out:structimpliesnew()and cannot be combined with it,classmeans non-nullable in a nullable context, at most one kind constraint and it must come first,new()must come last. Also the self-constraint pattern (where T : IAdditionSubtraction<T>) and why it exists: without it,static abstractoperators must be declared in terms of the interface, forcing implementers into explicit interface implementation. Use for: saying what a type parameter requires instead of leaving it implicit. - Docs: "Generic types and methods", Microsoft Learn
Covariance and contravariance explained as a rule about positions:out Tmay appear only in output positions andin Tonly in input positions, with workedIEnumerable<out T>andAction<in T>conversions and a list of the variant built-in types. Use for: knowing why a type with anAddcannot be variant. - Docs: "Generics in .NET", Microsoft Learn
The framework-level overview: what generics are for,Dictionary<TKey,TValue>againstHashtable, and pointers to generic math, generic interfaces and variance. Use for: orientation, and as the index to the numeric-interface material behindstatic abstractmembers. - Docs: "interface (C# Reference)", Microsoft Learn
Official reference for what an interface may declare (methods, properties, indexers, events, constants, operators, nested types, static members, explicit access modifiers) and what it may not: no instance state, no instance auto-properties, so a property declaration written like a class auto-property is instead an unimplemented requirement. Also interface inheritance, and that overriding a base interface's implemented method requires explicit interface implementation syntax. Use for: deciding what belongs in an interface rather than an abstract class. - Docs: "Safely update interfaces using default interface methods", Microsoft Learn
The tutorial that explains what default interface methods are for: adding a member to a published interface without breaking implementers, parameterising a default implementation with private static fields, and theprotected statichelper pattern that lets an implementer extend the default rather than replace it. Use for: evolving a library interface, and for the only kind of state a default implementation can keep. - Docs: "Explicit Interface Implementation", Microsoft Learn
Official guide to the case where one class member serves two interfaces with the same signature, and to the fix: an explicit implementation is a member only callable through the specified interface, so it leaves the class's own surface. Use for: disambiguating two interfaces, and for understanding which reference a default member is reached through. - Docs: "Patterns (C# reference)", Microsoft Learn
The whole pattern vocabulary in one place, usable fromis, theswitchstatement and theswitchexpression: declaration and type, constant, relational, logicaland/or/not, property (including the emptyis { }that matches anything non-null and can bind a variable), positional with the Important note that member order must matchDeconstruct's parameters, list patterns with the..slice, andvarand discard. Use for: taking a modelled type apart, and for choosing between a positional and a property pattern. - Docs: "switch expression (C# reference)", Microsoft Learn
Official reference for the value-producingswitch, including what an unmatched input throws (SwitchExpressionExceptionon .NET Core 3.0 and later,InvalidOperationExceptionon .NET Framework), that the compiler warns about uncovered inputs in most cases, and the exception to that: list patterns produce no such warning. Use for: knowing when a discard arm is a safety net and when it is the only one you have. - Docs: "Records (C# reference)", Microsoft Learn
Official reference for therecordmodifier over classes and structs: exactly what positional syntax generates (init-only or read-write properties depending on the form, a primary constructor, aDeconstructthat ignores non-positional properties,ToStringviaPrintMembers), value equality as compiler-synthesized rather than reflective, equality depending on the runtime type throughEqualityContract, shallow immutability with a worked mutable-array example, and the note that records are not appropriate as Entity Framework Core entity types because EF Core depends on reference equality. Use for: choosing between a record and a class, and for knowing what the generated members do and do not cover. - Docs: "C# record types", Microsoft Learn
The task-shaped companion, withwithexpressions for non-destructive mutation worked through for both record classes and record structs, including that awith { }copy compares equal to its original. Use for: the everyday shape of working with an immutable model. - Docs: "Properties (C# Programming Guide)", Microsoft Learn
Official guide to properties as a language member rather than a naming convention: auto-implemented properties, asymmetric accessor accessibility with the rule that an accessor must be more restrictive than the property, expression-bodied computed properties with no backing field, required properties with[SetsRequiredMembers], and the C# 14fieldkeyword for validating in an accessor without a hand-written backing field. Use for: modelling a type's surface, and for the warning not to readrequiredas non-nullable. - Docs: "The init keyword", Microsoft Learn
Reference for init-only setters: assignable only during construction, from a constructor or an object initializer, and more restrictive thanprivate set, which restricts who rather than when. Use for: immutability that still allows named initialisation, where Java would need a constructor andfinal. - Docs: "required modifier", Microsoft Learn
Reference forrequired(C# 11 and later): every construction expression must initialise the member, it applies to fields and properties on classes, structs and records but not to interface members, and setting it tonullsatisfies it. Use for: making initialisation mandatory without writing a constructor. - Docs: "Exception-handling statements (C# reference)", Microsoft Learn
Official reference fortry,catch,finallyandthrow, includingthrowas an expression, and thewhenexception filter with the section that justifies it: a filter does not unwind the stack, so a false filter leaves the original stack trace unchanged, while a clause that catches and rethrows has already lost it. Also the ordering rules a filter relaxes. Use for: handling only some exceptions of a type without destroying the trace. - Docs: "Handling and throwing exceptions in .NET", Microsoft Learn
The common-exceptions table with what each one's presence implies, including thatNullReferenceExceptionandIndexOutOfRangeExceptionare thrown by the runtime only, so they indicate a bug rather than a condition to handle. Use for: reading an exception type as a diagnosis. - Docs: "using statement (C# reference)", Microsoft Learn
Official reference forusingas C#'s deterministic cleanup: the instance is disposed when control leaves the block, explicitly including departure by exception, plus theusingdeclaration form andawait usingforIAsyncDisposable. Use for: the counterpart to Java's try-with-resources. - Docs: "Selection statements (C# reference)", Microsoft Learn
Official reference forifandswitch, including the two rules a Java background needs: every switch section must end withbreak,gotoorreturnand falling through is a compiler error, while multiple labels on one section are supported deliberately. Also that a switch matches patterns rather than only constants, and how the switch statement differs from the switch expression. Use for: writing aswitchwithout importing Java's fall-through. - Docs: "Iteration statements (C# reference)", Microsoft Learn
Official reference forfor,foreach,doandwhile, with the part worth reading closely:foreachneeds only a public parameterlessGetEnumerator(an extension method counts) returning a type withCurrentandMoveNext, so it works on types implementing no interfaces, and an explicitly typed iteration variable can throwInvalidCastExceptionat run time. Alsoawait foreach, which is duck-typed the same way (a public parameterlessGetAsyncEnumerator, possibly an extension member, returning a type withCurrentand a parameterlessMoveNextAsyncwhose return type isTask<bool>,ValueTask<bool>or any awaitable whose awaiter'sGetResultreturnsbool) and which processes elements in the captured context unlessTaskAsyncEnumerableExtensions.ConfigureAwaitdisables it. Use for: why a type can iterate without being queryable, and for the asynchronous loop's contract. - Docs: "Jump statements (C# reference)", Microsoft Learn
Official reference forbreak,continue,returnandgoto, includinggototo escape a nested loop andgoto caseinside a switch, with the tip to refactor nested loops into separate methods instead. Use for: the C# answer to Java's labelled break, which C# does not have. - Docs: "Collections and Data Structures", Microsoft Learn
Orientation for the collection types: the advice to prefer generic collections, a table matching a scenario to a type, algorithmic complexity for the mutable types against their immutable counterparts, and the statement that any type implementingIEnumerable<T>is a queryable type that LINQ can query. Use for: choosing a container, and for why LINQ is defined over an interface rather than over a class. - API: "Dictionary
", Microsoft Learn
Reference with the failure modes spelled out in its own example: the read indexer throwsKeyNotFoundExceptionfor an absent key,AddthrowsArgumentExceptionfor a duplicate one, andTryGetValueis recommended where misses are expected. Also the hash-table caveats, that retrieval speed depends onTKey's hashing and that a key must not change in a way that affects its hash while in use. Use for: the operation table every dictionary read should be checked against. - API: "IEnumerable
", Microsoft Learn
Reference for the one-method interface behindforeach, declaredIEnumerable<out T>, implemented byList<T>,Dictionary<TKey,TValue>,Stack<T>and the rest. Use for: writing a signature that asks only to enumerate, and as the definition the LINQ andIAsyncEnumerablelessons both build on. - Docs: "Built-in types (C# reference)", Microsoft Learn
The keyword-to-.NET-type table, with the statement that the keywords are aliases and interchangeable with the types they name (sointisSystem.Int32, andfloatisSystem.Single). Use for: reading any signature, and for why C# has no primitive-versus-wrapper split to import from Java. - Docs: "Floating-point numeric types (C# reference)", Microsoft Learn
Range, precision and size forfloat,doubleanddecimalside by side, plus which of them provide the not-a-number and infinity constants (floatanddoubledo,decimaldoes not). Use for: defending a numeric type choice from the numbers, money especially. - Docs: "String interpolation using $", Microsoft Learn
Official reference for$"...": the hole's grammar of expression, optional width and optional format string, that a null expression renders as the empty string, brace escaping, interpolated verbatim and raw string literals (including the multiple-$rule for embedding braces), and the compilation section with its warning that a handler might not evaluate every interpolation expression, so side effects might not occur. Use for: formatting output, and for why an interpolation hole must not do work. - Docs: "Nullable value types (C# reference)", Microsoft Learn
Official reference forT?asNullable<T>: that its default value represents null as an instance whoseHasValueis false, the four ways to get a value out and which of them throw, lifted operators propagating null (withbool?'s&and|as the exception), the rule that<,>,<=and>=all return false against null so opposite comparisons are not opposites, and boxing, which yields a null reference or the boxed underlying value rather than the wrapper. Use for: nullable numbers without the JavaIntegerinstinct, and for whyGetType()cannot see nullability. - Docs: "Task asynchronous programming model", Microsoft Learn
Official docs onasync/awaitand theTask-based model underneath it: what actually happens to a method's execution when it awaits. Use for: understandingasync/awaitas a mechanism, before comparing it to Java's virtual threads. - Docs: "Language Integrated Query (LINQ)", Microsoft Learn
Official docs for LINQ's query and method syntax over any enumerable source, with the three facts the rest of the arc leans on: a query is not executed until you iterate the query variable, the compiler converts query expressions into standard query operator calls so the two syntaxes have no semantic or performance difference, and a query compiles to a delegate or an expression tree depending on the type being queried, which is what lets a provider translate it into SQL. Use for: LINQ itself, and as the starting point for the LINQ-against-Stream-API comparison the mission names. - Docs: "ASP.NET Core fundamentals", Microsoft Learn
Official entry point for building and structuring an ASP.NET Core backend service. Use for: the concrete service-building context this workspace ships a typed, tested service against. - Docs: "Unit testing C# in .NET using dotnet test and xUnit", Microsoft Learn
The step-by-step tutorial that builds a library and its test project together, carrying the everyday xUnit vocabulary:[Fact], then the replacement of duplicated tests by a single[Theory]with one[InlineData]per case, and theAssert.False(result, message)form. Use for: the shape of a first test project, and as the primary source for the lesson on test frameworks. - Docs: "Unit testing C# with NUnit and .NET Core", Microsoft Learn
The same tutorial written against NUnit, which is what makes it useful:[TestFixture]denoting the class,[Test]the method, a[SetUp]method preparing per-test state,[TestCase]for a suite of tests that execute the same code with different input arguments, and the constraint-model assertionAssert.That(result, Is.False, message). Use for: reading NUnit after learning xUnit, and for the side-by-side comparison of the two vocabularies. - Docs: "Getting Started with xUnit.net v3", xUnit.net
The framework's own introduction, and the source of the distinction its attribute names encode: facts are tests which are always true, testing invariant conditions, while theories are tests which are only true for a particular set of data. Also the v3 project template and its package reference. Use for: why the attribute is not calledTest, and for what a current xUnit project file contains. - Docs: "Sharing Context between Tests", xUnit.net
The lifecycle document, and the one that explains the rest of xUnit: a new instance of the test class is created for every test, so constructor code runs for every single test andIDisposable.Disposeis the cleanup, withIAsyncLifetimewhere startup must be awaited and, in v3,IAsyncDisposablealone for async cleanup. Then the three widening scopes for sharing something expensive,IClassFixture<>, a[CollectionDefinition]class carryingICollectionFixture<>with[Collection]on each participating class, and the v3 assembly fixture, all delivered as a constructor argument, plus the note that only the assembly fixture leaves parallelization unchanged. Use for: deciding what a test may share, and for why xUnit has no setup attribute. - Docs: "Running Tests in Parallel", xUnit.net
The default parallel mode iscollectionswith a test collection per test class, so tests in one class do not run in parallel against each other while tests in different classes do, demonstrated with a measured 3-second and 5-second pair taking about 8 seconds together in one class and about 5 seconds in two. Also the assembly-level switches,CollectionBehaviorin v2 andParallelization(Mode = ...)in v3 4.0 and later. Use for: the cost side of a collection fixture, which is paid in wall-clock time. - Docs: "Attributes", NUnit
The complete attribute index, worth having open because NUnit's surface is attributes. Notable for the arc:[Theory]is a distinct feature fed by[Datapoint]and[DatapointSource]rather than a synonym for xUnit's, and the parallelism attributes sit here alongsideFixtureLifeCycle. Use for: resolving what an unfamiliar NUnit attribute does, and for the trap that a name shared with xUnit is not a meaning shared with it. - Docs: "FixtureLifeCycle", NUnit
The two-value table that states NUnit's default explicitly:LifeCycle.SingleInstance, one instance created and shared for all test cases, is the default, andLifeCycle.InstancePerTestCaseconstructs a new instance for each test case. Added in NUnit 3.13, and the documentation ties it to test case parallelism. Use for: the exact opposite of xUnit's rule, stated by the framework rather than inferred. - Docs: "SetUp", NUnit
[SetUp]marks a method called immediately before each test in the fixture, for per-test state that should not leak between cases, and it may bestaticor an instance method, an instance one running against the same shared fixture instance or a freshly constructed one underLifeCycle.InstancePerTestCase. Points at[TearDown],[OneTimeSetUp]and[OneTimeTearDown]. Use for: undoing NUnit's shared instance, which is what the attribute is for. - Docs: "Creating a substitute", NSubstitute
The most explicit warning any of these libraries publishes, and the reason substitution is a design subject rather than an API subject: the library can only work with virtual members overridable from the test assembly, so any non-virtual code in the class will actually execute, andReceived(),Returns(),Arg.Is(),Arg.Any()andWhen()..Do()do not work on non-overridable members, with the specific consequence thatsubClass.Received().NonVirtualCall()will not actually run an assertion and will always pass even when the call never happened. Recommends NSubstitute.Analyzers while noting it catches many such cases and not all. Use for: the failure mode where a test is green and means nothing. - Docs: "Getting started", NSubstitute
The basic shape,Substitute.For<ISomeInterface>()returning something already typed as the interface and configured by calling it (calculator.Add(1, 2).Returns(3)), plus the same warning in short form and the library's deliberate refusal of the stub-mock-fake-spy vocabulary. Use for: reading NSubstitute, and for the contrast with Moq's controller-plus-Objectshape. - Docs: "Quickstart", Moq
Moq's own reference by example:new Mock<IFoo>()as a controller configured throughSetupwithReturnsorThrowsand read throughmock.Object, argument matchers,SetupPropertyandSetupAllProperties,VerifySet,SetupSequencefor successive calls,MockBehavior.StrictwithMockSequencefor ordered expectations, andReset. Note that its assumptions block declares the class members it configures asvirtual, which is the overridability rule showing through the examples. Use for: reading and writing Moq, which is the library most existing C# test suites already use. - Docs: "What can be faked", FakeItEasy
The clearest short statement of the mechanism shared by all three libraries: fakes are made with Castle DynamicProxy, so anything that could normally be overridden, extended or implemented can be faked, which is interfaces, classes that are not sealed and not static and have a constructor the library can use, and delegates. Members can be overridden if they are virtual, abstract, or an interface method, and static members, including extension methods, cannot be. Use for: the boundary of substitution stated as a property of the type system rather than of a library. - Docs: "Best practices for writing unit tests", Microsoft Learn
Carries the terminology section, which is unusually honest about itself: the terms fake, stub and mock are used inconsistently across tools and literature, and in the .NET usage it documents a fake is the generic term, a stub is a controllable replacement for a dependency, and a mock is the object that decides whether or not a unit test passes or fails, remaining a fake until it enters anAssert. Also gives the narrower classical split from Fowler and Meszaros. Use for: settling an argument about a word, and for knowing when the argument is not worth having. - Docs: "virtual (C# Reference)", Microsoft Learn
One sentence carries the weight:virtualmodifies a method, property, indexer or event declaration and allows a derived class to override it, with the runtime then dispatching to the most derived override. Use for: the language default underneath every mocking question, and for the point where a Java background expects the opposite. - Docs: ".NET CLI overview", Microsoft Learn
The command index, grouped into basic commands (new,restore,build,publish,run,test,pack,clean,sln,watch,format) and project modification commands, which are listed noun-first (package add,package list,reference add,project convert). Use for: finding the command, and for seeing the .NET 10 command order in the index itself. - Docs: "dotnet test command", Microsoft Learn
Short and load-bearing:dotnet testbuilds the solution and runs the tests with either VSTest or Microsoft Testing Platform, and the test runner you use determines the available command-line options and behavior, with runner selection starting in the .NET 10 SDK and earlier versions always using VSTest. Use for: what a single command actually does, and for why two people can disagree about which flagsdotnet testaccepts. - Docs: "dotnet package add command", Microsoft Learn
Carries the implicit restore paragraph the whole CLI depends on:dotnet restoreis run implicitly by all commands that require it, includingdotnet new,dotnet build,dotnet run,dotnet test,dotnet publishanddotnet pack, with--no-restoreto disable it and continuous integration as the documented reason to restore explicitly. Also the scenario table for what the command writes under central package management, aPackageVersionintoDirectory.Packages.propsand a version-lessPackageReferenceinto the project. Use for: what a fresh clone has to type, and what adding a package edits. - Docs: "dotnet reference add command", Microsoft Learn
Adds project-to-project references, and states the version boundary plainly: on the .NET 9 SDK or earlier use the verb-first formdotnet add reference, because the noun-first form was introduced in .NET 10. Use for: the second kind of edge in a project file, and for why a copied command line fails on someone else's machine. - Docs: "PackageReference in project files", Microsoft Learn
The reference for package dependencies, whose most valuable section is the one on locking: aVersionis a floor rather than a pin, worked through as a two-day example whereVersion="4.0.0"resolves to 4.1.0 as the nearest minimum version while 4.0.0 is unpublished and switches to 4.0.0 once it exists. Also floating versions (3.6.*), theIncludeAssets,ExcludeAssetsandPrivateAssetstable for controlling what flows to consumers, andPrunePackageReference. Use for: why an unchanged project file can produce a changed build. - Docs: "Central Package Management", Microsoft Learn
The three-step onboarding: aDirectory.Packages.propsat the repository root withManagePackageVersionsCentrallyset totrue,PackageVersionitems declaring the versions, andPackageReferenceitems in each project with noVersionattribute, withdotnet new packagespropsto create the file. Use for: one version per package across a repository, and for knowing that a project file has stopped being a complete answer. - Docs: "Routing in ASP.NET Core", Microsoft Learn
Carries the position rule that decides what a middleware can see: routing is the pairUseRouting, which adds route matching and selects the best match, andUseEndpoints, which runs the selected endpoint's delegate; the endpoint is always null beforeUseRouting, non-null between the two, andUseEndpointsis terminal when a match is found so middleware after it execute only when no match is found. Also thatWebApplicationBuilderwraps middleware added inProgram.cswith both, so most apps never call either, and route template precedence, where more segments, a literal over a parameter, and a constrained parameter over an unconstrained one decide the match instead of registration order. Use for: reading aProgram.cs, and for the two questions people otherwise debug by trial. - Docs: "ASP.NET Core middleware", Microsoft Learn
The pipeline itself:Usewith itsawait next.Invoke(context)splitting a component into a half that can write to the response and a half afternextthat does not,Runas the terminal delegate, and the three branching calls with their request-and-response tables. The details worth remembering are thatMapmatches when the request path starts with the given path and removes the matched segments fromHttpRequest.Path, appending them toHttpRequest.PathBase, and thatUseWhen, unlikeMapWhen, rejoins the main pipeline unless its branch short-circuits or contains a terminal middleware. Use for: predicting which lines run for a request, and for the refactor that breaks a middleware by moving it into a branch. - Docs: "Dependency injection guidelines", Microsoft Learn
The anti-patterns page, and the one to reach for when a lifetime is in question. Captive dependency, a longer-lived service holding a shorter-lived one, with the singleton-holding-a-scoped example the docs call valid-looking and wrong, andvalidateScopes: trueproducing anInvalidOperationException. Scoped service as singleton, where resolving outside any scope makes it one. Disposal: the container cleans up what it creates, transient and scoped at the end of their scope and singletons at shutdown, services resolved from the container should never be disposed by the developer, receiving anIDisposabledependency does not oblige the receiver to implement one, and scopes are not hierarchical. Thread safety: resolving is safe, which only guarantees that constructing and resolving services is safe and leaves shared mutable state in a singleton to be synchronised by its author. Plus the recommendations against the service locator pattern and against storing data or configuration in the container. Use for: choosing a lifetime, and for the failure that compiles. - Docs: "Dependency injection in ASP.NET Core", Microsoft Learn
The framework half:AddTransient,AddScopedandAddSingletonworked through with one class registered under three lifetimes so the identifiers can be compared per request. Carries the middleware rule that follows from a middleware being built once for the app: use scoped services by injecting them intoInvokeorInvokeAsync, because constructor injection throws a runtime exception, since it forces the scoped service to behave like a singleton, with factory-based middleware, activated per client request, as the alternative that allows constructor injection. Use for: registering services, and for the one place the framework refuses a captive dependency outright. - Docs: ".NET dependency injection", Microsoft Learn
The container without the web framework, whose scope-validation section states the rule the rest of the subject depends on: a scoped service created in the root container has its lifetime effectively promoted to singleton, and an app running in the development environment withCreateApplicationBuilderchecks that scoped services are neither resolved from the root provider nor injected into singletons. Also constructor injection behaviour, where a public constructor is required and arguments not supplied by DI must have default values. Use for: the boundary of the automatic check, which is narrower than it first reads. - Docs: "Configuration in ASP.NET Core", Microsoft Learn
The model in four sentences it states plainly: configuration values are strings, null values can't be stored in configuration or bound to objects, keys are case-insensitive, and where several providers set the same key the value from the last provider added is used, since sources are read in the order their providers are specified. Also the delimiter table that costs people an afternoon, a colon everywhere in the Configuration API, a double underscore for environment variables because a colon does not work on all platforms and is automatically converted, and a double dash in Key Vault; plus the typical provider sequence ending with the command line, which overrides every other provider by default. Use for: answering why production disagrees with the file in front of you. - Docs: "Options pattern in ASP.NET Core", Microsoft Learn
Binding a section to a class withConfigure<TOptions>(configuration.GetSection(...)), and the comparison of the three interfaces, which differ by lifetime:IOptions<T>is a singleton that does not support reading configuration after the app has started,IOptionsSnapshot<T>is scoped and therefore cannot be injected into a singleton, giving a snapshot taken when the object is constructed, andIOptionsMonitor<T>is a singleton retrieving current values at any time, with change notifications, described as especially useful in singleton dependencies. Carries the validation timing too: validation runs the first time aTOptionsinstance is created and again on every reload, unlessValidateOnStartmoves it to startup. Named options are case sensitive, unlike configuration keys. Use for: choosing an options interface from the lifetime of the class reading it, and for making a bad setting fail the deployment instead of the request. - Docs: "DbContext Lifetime, Configuration, and Initialization", EF Core
The context as a unit of work: its lifetime begins at construction and ends at disposal, an instance is for a single unit of work so usually very short, and in a web app each HTTP request is one, which is whyAddDbContextregisters it scoped and why the context needs a public constructor takingDbContextOptions<T>. Also the threading section, which is the one to quote: EF Core does not support multiple parallel operations on the sameDbContextinstance, including parallel async queries, so always await async calls immediately or use separate instances, with anInvalidOperationExceptionwhen detected and undefined behavior, crashes and data corruption when not. Scoped registration is safe only because one thread executes a request at a time. Use for: why the context is scoped, and for the one place stage 4's overlap habit is wrong. - Docs: "Change Tracking in EF Core", EF Core
TheEntityStatetable (Detached,Added,Unchanged,Modified,Deleted) against whatSaveChangesdoes with each, with the two facts that explain why nobody calls an update method: all entities returned from queries are initiallyUnchanged, and EF Core tracks changes at the property level, so one modified property updates one column. Use for: reading a save path that contains no save call. - Docs: "Tracking vs. No-Tracking Queries", EF Core
AsNoTrackingpresented as a behaviour change rather than a speed switch: no-tracking queries do not do identity resolution and return a new instance of the entity even when the same entity is in the result multiple times, and their results are based on what is in the database, disregarding any local changes or added entities.AsNoTrackingWithIdentityResolutionrestores the first half. Use for: read-only paths, and for the surprise that two rows for one customer are two objects. - Docs: "Client vs. Server Evaluation", EF Core
Where a LINQ query stops being SQL: EF Core supports partial client evaluation in the top-level projection, essentially the lastSelect(), and throws a runtime exception for anything untranslatable elsewhere, a rule that only exists from version 3.0 onwards. Also the memory-leak section, where constants captured by a client-evaluated projection stay alive in the cached query plan, with the fix of making the method static or passing scalar arguments. Use for: the boundary lesson 13's expression trees run into, and for why the same helper throws inWhereand succeeds inSelect. - Docs: "Migrations Overview", EF Core
Schema changes as generated, reviewable code:dotnet ef migrations add InitialCreatecreates aMigrationsdirectory and generates the files, and the documentation's own advice is to inspect what EF Core generated, and possibly amend it. Points at the CLI tools reference for update, drop, add and remove. Use for: the first migration, and for the reminder that a generated migration is a change to production data waiting to be read. -
Docs: "Integration tests in ASP.NET Core", Microsoft Learn
Testing the assembled service rather than its parts, and it gives the rule for choosing: integration tests use the actual components that the app uses in production, take longer and cost more code, so limit them to the most important infrastructure scenarios and if a behavior can be tested using either a unit test or an integration test, choose the unit test. The mechanism isWebApplicationFactory<TEntryPoint>over an in-memoryTestServerfromMicrosoft.AspNetCore.Mvc.Testing, customised by overridingConfigureWebHostand editing the service collection, where the sample finds the service descriptor for the database context and removes the registration before adding a test one, with the factory held by a test class throughIClassFixture<T>andCreateClientreturning anHttpClient. Use for: testing through the container's own seam, and for knowing what such a suite does not prove. -
Docs: "Virtual Threads", Oracle Java SE 21
The Java side of the concurrency comparison, in its own words. A virtual thread is not tied to a specific OS thread; the runtime mounts it on a platform thread called a carrier and it unmounts when it performs a blocking I/O operation. The two sentences that settle most arguments: virtual threads are not faster threads, they exist to provide scale (higher throughput), not speed (lower latency), and the adoption guide's instruction to write simple, synchronous code employing blocking I/O APIs, since non-blocking asynchronous code will not benefit much from virtual threads. Also the costs: pinning inside asynchronizedblock or native code, which cannot unmount and may hurt throughput when the blocking is long-lived and frequent, and the rule never to pool virtual threads because the number of virtual threads is always equal to the number of concurrent tasks. Use for: the comparison lesson, and for refusing the claim that virtual threads makeasync/awaitunnecessary in C#. -
Docs: "java.util.stream", Oracle Java SE 21
The Java side of the query comparison. Streams have no storage, are functional in nature, and their intermediate operations are always lazy, with traversal beginning only when the terminal operation executes, all of which matches LINQ. The divergence is in the next sentence: after the terminal operation the pipeline is considered consumed and can no longer be used, so you must return to the data source to get a new stream. Parallelism is a property of the stream, requested withparallelStream()orparallel(), since the JDK creates serial streams unless parallelism is explicitly requested. Use for: what LINQ and Streams genuinely share, and the two places they part. -
Docs: "Common C# code conventions", Microsoft Learn
The conventions the .NET docs team writes its own samples to, chosen for correctness, teaching, consistency and adoption. Most useful for the Tools and analyzers section, which makes the mechanical half enforceable rather than reviewable: enable code analysis, add an.editorconfig, and each CI build notifies developers when they violate any of the rules. The judgment-shaped guidance is worth quoting in review: only catch exceptions that can be properly handled, avoid catching general exceptions and neverSystem.Exceptionwithout a filter, useasyncandawaitfor I/O-bound operations, and use implicit typing only when the type is obvious from the right side of the assignment, with the explicit warning not to assume the type is clear from a method name. Use for: deciding what a person should review and what a build should.
Gaps
- Still no single source contrasting C#'s
async/awaitwith Java 21 virtual threads, or LINQ with the Stream API. Lesson 30 builds both comparisons from each side's own primary documentation and says so in the lesson, attributing every claim to the platform it came from; the comparison itself is argued rather than cited. Worth revisiting if a credible source ever covers both sides at once. - Nothing here covers parallel LINQ, so the parallelism row of lesson 30's query comparison is deliberately one-sided. Close this before any lesson claims a C# counterpart to
parallelStream().