Skip to content
teach

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 for Span<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 crossing await/yield) that guarantee it. Use for: what a ref struct actually forbids, and why.
  • Docs: "Memory and Span usage guidelines", Microsoft Learn
    Official guidance for Memory<T> as Span<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 with Span<T>/ReadOnlySpan<T> needs no unsafe context, the automatic buffer-overrun detection the CLR enables when it's used, and the StackOverflowException risk from over-allocating. Use for: what stackalloc actually does, and its real failure mode.
  • API: "ArrayPool Class", Microsoft Learn
    Official API reference for the shared array pool: the rent/return contract, that Rent may hand back a larger array than requested but never a smaller one, and that there is no finalizer safety net for a forgotten Return. Use for: the fallback for a buffer too large or too long-lived for stackalloc.
  • 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 for MemoryDiagnoser: bytes allocated per operation via GC.GetAllocatedBytesForCurrentThread, 99.5% accuracy at default settings, the GenX collections-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 where dotnet-counters and dotnet-trace each 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 dedicated gc-verbose/gc-collect profiles, and why the older cpu-sampling profile 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 on EventCounter/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 public Dispose()/protected Dispose(bool disposing) split, what the disposing parameter changes about what's safe to touch, and when a finalizer is (and isn't) worth adding. Use for: implementing IDisposable correctly, not just consuming it.
  • Docs: "Implement a DisposeAsync method", Microsoft Learn
    Official reference for the async half: DisposeAsyncCore() for a non-sealed class, why DisposeAsync() calls Dispose(false) rather than Dispose(true), idempotency, and cascading disposal through a chain of owned objects. Use for: implementing IAsyncDisposable alongside (or instead of) IDisposable.
  • Docs: "Logging in .NET and ASP.NET Core", Microsoft Learn
    Official docs for ILogger'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's ILogger, Meter, and Activity/ActivitySource APIs 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 for ActivitySource/Activity as .NET's Tracer/Span, and the documented performance optimization where StartActivity() returns null with 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 the ClaimsPrincipal, and the UseAuthentication middleware-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 = "...")] or RequireAuthorization(...). 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 for JsonSerializerContext, [JsonSourceGenerationOptions], and the gotcha where an explicit JsonSerializerOptions instance 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 for DiagnosticId and UrlFormat, the .NET runtime's own SYSLIB0XXX/EXTOBS0XXX conventions, 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 a void member, constructor, finalizer or set/init/add/remove accessor needs a statement expression (assignment, invocation, object creation, increment or decrement, or await) 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 the ToArray is 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 bare Select returning 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 the WhenAny interleaving loop, which awaits WhenAny, removes the completed task from the list, then awaits that task for its value. Use for: processing results as they arrive, and for why WhenAny needs two awaits.
  • Docs: "await operator", Microsoft Learn
    The mechanism in one paragraph: await suspends 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 of await t for each task type, that a faulted operand has its exception rethrown rather than wrapped, and the three places await is forbidden (a synchronous local function, a lock block, an unsafe context). 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 accessible GetAwaiter), the parameter restrictions (no in, ref, ref readonly or out, and no reference return), and the guidance on async 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 against async void in review.
  • Docs: "Task-based asynchronous programming", Microsoft Learn
    The Task type itself, before any keywords: a task as an asynchronous operation at a higher level of abstraction than a thread, queued to the thread pool; Status and the TaskStatus enumeration; creation with Task.Run and TaskFactory.StartNew; tasks without delegates via TaskCompletionSource<TResult> and FromAsync; the blocking family (Wait, WaitAll, WaitAny, Result) against ContinueWith and the asynchronous WhenAll/WhenAny with WhenAny's four documented scenarios; and the exception model, where failures are wrapped in an AggregateException delivered to the joining thread, with the warning that reading Exception before garbage collection is what stops an unobserved failure terminating the process. Use for: what an await is actually awaiting.
  • Docs: "Generate and consume async streams", Microsoft Learn
    The tutorial that converts a Task<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, with ValueTask used in them for performance reasons; yield return in a method declared async; and the finished-application comparison, where the first page is enumerated as soon as it is available, no try/catch is 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 other async methods, plus [EnumeratorCancellation], which causes the compiler to generate code making the token passed to GetAsyncEnumerator visible 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 for yield return and yield break, with iteration also finishing when control reaches the end of an iterator, and the restrictions: no yield in methods with in, ref or out parameters, 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 around CancellationTokenSource, that cancellation refers to operations rather than objects and means stop as soon as possible after any required cleanup, that IsCancellationRequested cannot be reset so a token cannot be reused after cancellation, and Register with its CancellationTokenRegistration for 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 in RanToCompletion rather than Canceled, while throwing OperationCanceledException carrying the requested-on token, preferably via ThrowIfCancellationRequested, transitions it to Canceled so 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 a TaskCanceledException from Wait or WaitAll indicates successful cancellation rather than a fault. Use for: making cancellation observable, and for why the token is passed both into the delegate and to Task.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 following if, 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, string and string? are both System.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 against Nullable<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 or out parameters 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: EventHandler and EventHandler<TEventArgs>, event data classes named with an EventArgs suffix, and EventArgs.Empty for 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 with from and end with select or group, with where, orderby, join, let and further from clauses between, and into to 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 same IEnumerable<T> query-variable type, and the observation that IEnumerable<T> has no Where method 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, the this first parameter and the C# 14 extension block, 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 for extension blocks: 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: struct implies new() and cannot be combined with it, class means 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 abstract operators 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 T may appear only in output positions and in T only in input positions, with worked IEnumerable<out T> and Action<in T> conversions and a list of the variant built-in types. Use for: knowing why a type with an Add cannot be variant.
  • Docs: "Generics in .NET", Microsoft Learn
    The framework-level overview: what generics are for, Dictionary<TKey,TValue> against Hashtable, and pointers to generic math, generic interfaces and variance. Use for: orientation, and as the index to the numeric-interface material behind static abstract members.
  • 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 the protected static helper 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 from is, the switch statement and the switch expression: declaration and type, constant, relational, logical and/or/not, property (including the empty is { } that matches anything non-null and can bind a variable), positional with the Important note that member order must match Deconstruct's parameters, list patterns with the .. slice, and var and 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-producing switch, including what an unmatched input throws (SwitchExpressionException on .NET Core 3.0 and later, InvalidOperationException on .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 the record modifier over classes and structs: exactly what positional syntax generates (init-only or read-write properties depending on the form, a primary constructor, a Deconstruct that ignores non-positional properties, ToString via PrintMembers), value equality as compiler-synthesized rather than reflective, equality depending on the runtime type through EqualityContract, 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, with with expressions for non-destructive mutation worked through for both record classes and record structs, including that a with { } 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# 14 field keyword for validating in an accessor without a hand-written backing field. Use for: modelling a type's surface, and for the warning not to read required as 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 than private set, which restricts who rather than when. Use for: immutability that still allows named initialisation, where Java would need a constructor and final.
  • Docs: "required modifier", Microsoft Learn
    Reference for required (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 to null satisfies it. Use for: making initialisation mandatory without writing a constructor.
  • Docs: "Exception-handling statements (C# reference)", Microsoft Learn
    Official reference for try, catch, finally and throw, including throw as an expression, and the when exception 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 that NullReferenceException and IndexOutOfRangeException are 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 for using as C#'s deterministic cleanup: the instance is disposed when control leaves the block, explicitly including departure by exception, plus the using declaration form and await using for IAsyncDisposable. Use for: the counterpart to Java's try-with-resources.
  • Docs: "Selection statements (C# reference)", Microsoft Learn
    Official reference for if and switch, including the two rules a Java background needs: every switch section must end with break, goto or return and 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 a switch without importing Java's fall-through.
  • Docs: "Iteration statements (C# reference)", Microsoft Learn
    Official reference for for, foreach, do and while, with the part worth reading closely: foreach needs only a public parameterless GetEnumerator (an extension method counts) returning a type with Current and MoveNext, so it works on types implementing no interfaces, and an explicitly typed iteration variable can throw InvalidCastException at run time. Also await foreach, which is duck-typed the same way (a public parameterless GetAsyncEnumerator, possibly an extension member, returning a type with Current and a parameterless MoveNextAsync whose return type is Task<bool>, ValueTask<bool> or any awaitable whose awaiter's GetResult returns bool) and which processes elements in the captured context unless TaskAsyncEnumerableExtensions.ConfigureAwait disables 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 for break, continue, return and goto, including goto to escape a nested loop and goto case inside 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 implementing IEnumerable<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 throws KeyNotFoundException for an absent key, Add throws ArgumentException for a duplicate one, and TryGetValue is recommended where misses are expected. Also the hash-table caveats, that retrieval speed depends on TKey'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 behind foreach, declared IEnumerable<out T>, implemented by List<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 and IAsyncEnumerable lessons 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 (so int is System.Int32, and float is System.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 for float, double and decimal side by side, plus which of them provide the not-a-number and infinity constants (float and double do, decimal does 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 for T? as Nullable<T>: that its default value represents null as an instance whose HasValue is false, the four ways to get a value out and which of them throw, lifted operators propagating null (with bool?'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 Java Integer instinct, and for why GetType() cannot see nullability.
  • Docs: "Task asynchronous programming model", Microsoft Learn
    Official docs on async/await and the Task-based model underneath it: what actually happens to a method's execution when it awaits. Use for: understanding async/await as 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 the Assert.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 assertion Assert.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 called Test, 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 and IDisposable.Dispose is the cleanup, with IAsyncLifetime where startup must be awaited and, in v3, IAsyncDisposable alone for async cleanup. Then the three widening scopes for sharing something expensive, IClassFixture<>, a [CollectionDefinition] class carrying ICollectionFixture<> 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 is collections with 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, CollectionBehavior in v2 and Parallelization(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 alongside FixtureLifeCycle. 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, and LifeCycle.InstancePerTestCase constructs 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 be static or an instance method, an instance one running against the same shared fixture instance or a freshly constructed one under LifeCycle.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, and Received(), Returns(), Arg.Is(), Arg.Any() and When()..Do() do not work on non-overridable members, with the specific consequence that subClass.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-Object shape.
  • Docs: "Quickstart", Moq
    Moq's own reference by example: new Mock<IFoo>() as a controller configured through Setup with Returns or Throws and read through mock.Object, argument matchers, SetupProperty and SetupAllProperties, VerifySet, SetupSequence for successive calls, MockBehavior.Strict with MockSequence for ordered expectations, and Reset. Note that its assumptions block declares the class members it configures as virtual, 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 an Assert. 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: virtual modifies 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 test builds 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 flags dotnet test accepts.
  • Docs: "dotnet package add command", Microsoft Learn
    Carries the implicit restore paragraph the whole CLI depends on: dotnet restore is run implicitly by all commands that require it, including dotnet new, dotnet build, dotnet run, dotnet test, dotnet publish and dotnet pack, with --no-restore to 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, a PackageVersion into Directory.Packages.props and a version-less PackageReference into 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 form dotnet 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: a Version is a floor rather than a pin, worked through as a two-day example where Version="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.*), the IncludeAssets, ExcludeAssets and PrivateAssets table for controlling what flows to consumers, and PrunePackageReference. Use for: why an unchanged project file can produce a changed build.
  • Docs: "Central Package Management", Microsoft Learn
    The three-step onboarding: a Directory.Packages.props at the repository root with ManagePackageVersionsCentrally set to true, PackageVersion items declaring the versions, and PackageReference items in each project with no Version attribute, with dotnet new packagesprops to 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 pair UseRouting, which adds route matching and selects the best match, and UseEndpoints, which runs the selected endpoint's delegate; the endpoint is always null before UseRouting, non-null between the two, and UseEndpoints is terminal when a match is found so middleware after it execute only when no match is found. Also that WebApplicationBuilder wraps middleware added in Program.cs with 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 a Program.cs, and for the two questions people otherwise debug by trial.
  • Docs: "ASP.NET Core middleware", Microsoft Learn
    The pipeline itself: Use with its await next.Invoke(context) splitting a component into a half that can write to the response and a half after next that does not, Run as the terminal delegate, and the three branching calls with their request-and-response tables. The details worth remembering are that Map matches when the request path starts with the given path and removes the matched segments from HttpRequest.Path, appending them to HttpRequest.PathBase, and that UseWhen, unlike MapWhen, 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, and validateScopes: true producing an InvalidOperationException. 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 an IDisposable dependency 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, AddScoped and AddSingleton worked 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 into Invoke or InvokeAsync, 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 with CreateApplicationBuilder checks 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 with Configure<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, and IOptionsMonitor<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 a TOptions instance is created and again on every reload, unless ValidateOnStart moves 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 why AddDbContext registers it scoped and why the context needs a public constructor taking DbContextOptions<T>. Also the threading section, which is the one to quote: EF Core does not support multiple parallel operations on the same DbContext instance, including parallel async queries, so always await async calls immediately or use separate instances, with an InvalidOperationException when 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
    The EntityState table (Detached, Added, Unchanged, Modified, Deleted) against what SaveChanges does with each, with the two facts that explain why nobody calls an update method: all entities returned from queries are initially Unchanged, 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
    AsNoTracking presented 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. AsNoTrackingWithIdentityResolution restores 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 last Select(), 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 in Where and succeeds in Select.
  • Docs: "Migrations Overview", EF Core
    Schema changes as generated, reviewable code: dotnet ef migrations add InitialCreate creates a Migrations directory 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 is WebApplicationFactory<TEntryPoint> over an in-memory TestServer from Microsoft.AspNetCore.Mvc.Testing, customised by overriding ConfigureWebHost and 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 through IClassFixture<T> and CreateClient returning an HttpClient. 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 a synchronized block 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 make async/await unnecessary 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 with parallelStream() or parallel(), 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 never System.Exception without a filter, use async and await for 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/await with 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().
Table of contents