Advanced C# — Mission
Sections
Reading settings
Natural voice recorded for this course, the same in every browser.
No voice for this language in this browser.
More voices
Code and English terms
Off: the page’s voice reads everything, with no pause between two voices.
No voice for this language in this browser.
More voices
More voices: Microsoft Edge has natural online voices, and Windows (Settings › Time & language › Speech), macOS and Android install others.
Why I’m learning this
Section titled “Why I’m learning this”I have written C# for years, and most of what I know about its performance is folklore: “structs are faster”, “avoid LINQ”, “always ConfigureAwait(false)”, “FrozenDictionary is the fast one”. Some of it was true in .NET Framework 4.5 and is wrong on .NET 10, where the JIT removes allocations the IL asks for. This course replaces each piece of folklore with something I can look at: the size of an object, the IL the compiler emitted, the state machine behind await, the generation of an array, a BenchmarkDotNet table.
The same goes for the code that moves data between tasks and for ASP.NET Core. I use channels and BackgroundService without having checked what happens when a consumer stops early or a producer throws, and I configure ASP.NET Core by copying what worked last time. The later parts of the course run those questions as programs too.
The measurements run on real code, not toy classes: Guitar Alchemist (GA), a large .NET 10 code base about music theory. Its value objects, its caches, its voicing generator and its hosted services are exactly the kind of code where these questions come up, and the lessons found several places where GA pays for something it didn’t intend to, or hangs where it should fail.
Who this course is for
Section titled “Who this course is for”You write C# every day and know the language well: generics, LINQ, async/await, records, pattern matching. You want to know what the compiler, the JIT, the garbage collector and ASP.NET Core do with that code, and to measure before you optimize. If you are starting with C#, begin with the C# for beginners course, which ends where this one starts.
If you also work with Java, parts 2 and 3 end each lesson with an “If you know Spring and Reactor” table, from C# to Java. The Spring Boot, Spring Cloud and Reactor course goes the other way, from ASP.NET Core to Spring; the two courses link to each other instead of explaining the same thing twice.
By the end of this course, I will be able to
Section titled “By the end of this course, I will be able to”- predict the size of a value or an object, spot boxing in IL, and tell when the JIT removes it;
- use
ref,in,ref readonly,Span<T>andstackallocwithout defensive copies or escaping references; - explain generations, the large and pinned object heaps and the GC modes, and read
GC.GetGCMemoryInfo; - read the state machine the compiler generates for an
asyncmethod, choose betweenTaskandValueTask, and avoid deadlocks and lost cancellations; - write a BenchmarkDotNet benchmark that measures what I think it measures, and interpret tiered compilation and PGO;
- choose between
Dictionary,FrozenDictionary,SearchValuesand plain arithmetic, and vectorize a loop withVector<T>orTensorPrimitives; - connect producers and consumers with channels, TPL Dataflow, Rx.NET or
IAsyncEnumerable, and say for each what happens under backpressure, on an error and on cancellation; - follow a request through ASP.NET Core, from Kestrel to the endpoint, and choose service lifetimes, options, filters, hosted services, resilience and caching deliberately;
- observe and test an ASP.NET Core service, and publish it with Native AOT;
- and, in the last part: expression trees, source generators, Roslyn analyzers and interop.
The running example
Section titled “The running example”Parts 1 and 2 measure GA’s own code. Part 3 builds a small scales and chords service on GA’s domain types, the ASP.NET Core counterpart of the scales service in the Spring Boot, Spring Cloud and Reactor course: the same endpoints, so that each lesson can compare the two implementations line by line.
Outline
Section titled “Outline”Part 1: runtime and performance
Section titled “Part 1: runtime and performance”| # | Lesson | Under the hood | Measured on GA | Spring and Reactor |
|---|---|---|---|---|
| 1 | Memory: values, references and spans | object layout, boxing in IL, ref/in, ref struct, Span<T>, stackalloc |
PitchClass, PitchClassSetId.ItemsSpan |
no value types, erased generics, releasing a DataBuffer |
| 2 | The garbage collector | generations, LOH and POH, workstation and server GC, DATAS, finalizers, GC.GetGCMemoryInfo |
allocations of ItemsSpan with [MemoryDiagnoser] |
G1 and ZGC, Cleaner, JMH’s -prof gc |
| 3 | async and await under the hood | the generated state machine, ValueTask, SynchronizationContext, ConfigureAwait, cancellation, IAsyncEnumerable |
Try.OfAsync, LazyWithExpiration |
assembly and subscription, publishOn, virtual threads |
| 4 | Measured performance | BenchmarkDotNet, tiered JIT and PGO, SearchValues, FrozenDictionary, Vector<T> |
PitchClass subtraction, SimdOps.Dot |
JMH, C1 and C2, the Vector API |
| 5 | Generics in depth | constraints, static abstract members, generic math, allows ref struct, how the JIT shares generic code |
GA’s IStaticValueObjectList<TSelf> |
erasure, and generics without primitives |
Part 2: concurrency and data flow
Section titled “Part 2: concurrency and data flow”| # | Lesson | Under the hood | Spring and Reactor |
|---|---|---|---|
| 6 | Channels | bounded and unbounded channels, full modes, completion and errors, several producers and consumers, cancellation; GA’s voicing generator and index command | onBackpressureBuffer, onBackpressureDrop, BlockingQueue |
| 7 | TPL Dataflow | blocks and links, parallelism and order, BoundedCapacity, faults that only flow downstream, completion; GA’s Dataflow demo |
flatMap with concurrency, buffer, publishOn |
| 8 | Rx.NET | IObservable<T>, cold and hot, operators in virtual time, schedulers, no backpressure, retries; GA’s reactive demo |
Flux, Sinks, publishOn, StepVerifier.withVirtualTime |
| 9 | Choosing a stream | IAsyncEnumerable and System.Linq.AsyncEnumerable, the four stream types measured side by side, bridges, throughput, a decision chart |
Reactor: Mono and Flux, Reactor under the hood |
| 10 | Shared state and the thread pool | System.Threading.Lock, Interlocked, concurrent collections, Parallel.ForEachAsync, thread pool starvation |
synchronized, ReentrantLock, virtual threads |
Part 3: ASP.NET Core in depth
Section titled “Part 3: ASP.NET Core in depth”| # | Lesson | Under the hood | Spring and Reactor |
|---|---|---|---|
| 11 | Hosting, WebApplication and Kestrel |
the generic host, the builder, Kestrel’s connection and request limits, graceful shutdown | Spring Boot seen from ASP.NET Core, embedded Tomcat and Netty |
| 12 | The middleware pipeline | Use, Map, Run, ordering, short-circuits, exception handling, endpoint routing |
Servlet filters, WebFilter |
| 13 | Dependency injection and options | lifetimes, captive dependencies, scope validation, keyed services, IOptions, IOptionsSnapshot, IOptionsMonitor, validation |
the Spring container, @ConfigurationProperties |
| 14 | Minimal APIs and controllers | route handlers and parameter binding, filters, validation, TypedResults, streaming IAsyncEnumerable responses |
@RestController, WebFlux functional endpoints |
| 15 | Hosted services | IHostedService, BackgroundService, startup and shutdown order, exceptions, queues with channels; GA’s hosted services |
@Scheduled, SmartLifecycle |
| 16 | Authentication and authorization | schemes, handlers, JWT bearer, policies and requirements | Spring Security |
| 17 | Petri specification oracles for C# pipelines | finite lifecycle models, complete reachability, terminal classification, deterministic runtime gates; Channel queues, Dataflow execution/output retention and Rx assumptions kept distinct | the same method applies to Reactor only after its capacity and scheduler semantics are modeled explicitly |
| 18 | gRPC and SignalR | protobuf contracts, streaming calls, hubs, backpressure over the network | Spring gRPC, WebSocket, RSocket |
| 19 | Resilience, rate limiting and output caching | Microsoft.Extensions.Http.Resilience, Polly pipelines, rate limiters, output cache policies |
Resilience4j, Spring Cloud Circuit Breaker |
| 20 | OpenTelemetry and diagnostics in production | System.Diagnostics.Metrics, ActivitySource, OpenTelemetry exporters, dotnet-counters, dotnet-trace, dotnet-dump |
Micrometer, Actuator |
| 21 | Testing with WebApplicationFactory |
the test host, replacing services, authentication in tests, Testcontainers | @SpringBootTest, WebTestClient |
| 22 | Native AOT and trimming | publishing an API with Native AOT, trimming warnings, the request delegate generator, startup and size measured | GraalVM native images |
Part 4: metaprogramming and tooling
Section titled “Part 4: metaprogramming and tooling”| # | Lesson | Under the hood | Spring and Reactor |
|---|---|---|---|
| 23 | Expression trees, reflection and source generators | what a lambda compiles to, Expression<T>, the cost of reflection, incremental generators, [GeneratedRegex] |
annotation processors, Spring’s AOT engine |
| 24 | Roslyn analyzers and code fixes | syntax and semantic models, writing an analyzer and its tests | Error Prone, SpotBugs |
| 25 | Interop and unsafe code | [LibraryImport], function pointers, Unsafe, MemoryMarshal, pinning |
JNI, and the foreign function and memory API |
Appendices
Section titled “Appendices”| # | Page | Under the hood | Measured on GA |
|---|---|---|---|
| 1 | Five optimisations, proved then measured | rotations and PopCount on 12-bit sets, lookup tables, an equivalence proof over the whole input domain, stable sorts as a tie-break, and a benchmark that measured the JIT instead of the code |
IsClusterFree, IntervalClassVector, ClosestDiatonicKey, ToNormalForm, PrimeForm |
| 2 | GA, profiled, then proved and measured | dotnet-trace on a real pipeline, 12-bit masks instead of hash sets, boxed enumerators, a 4096-slot cache keyed by the set itself, a LINQ property in a hot loop, and a proof that compares two builds of GA byte for byte |
CanonicalChordRecognizer, ChordIntervalPattern.TryMatch, IntervalClassVector, OptickIndexReader |
| 3 | Petri-net lifecycle oracle | one bounded pipeline with explicit success, failure and cancellation; complete reachability and no non-terminal dead marking | the Channel failure shapes studied in lessons 6 and 9 |
| 4 | Plugins without MEF | one collectible AssemblyLoadContext per module, AssemblyDependencyResolver, one dependency-injection container, two versions of a library side by side, type identity, and a timer that keeps its module loaded |
not GA: four small modules written for the appendix |
| — | Journal |
Lessons 18 to 25 are planned and not written yet. Lesson 17 is an advanced concurrency lab added after the ASP.NET Core lessons; lessons 6 to 9 were written before lesson 5 and don’t depend on it.
Prerequisites
Section titled “Prerequisites”- The .NET 10 SDK and Git. On Windows, run the course scripts from Git Bash.
check.shrestoresilspycmdas a local .NET tool (version 11.0.0.9375, pinned in.config/dotnet-tools.json), fetches the three GA projects the program uses, about 11 MB, and restoresSystem.Reactive7.0.0 for lesson 8, andNewtonsoft.Json13.0.3 and 13.0.1 for appendix 4.- For the benchmarks, a machine you can keep quiet for a few minutes: close the browser, plug in the laptop.
Related courses on this site
Section titled “Related courses on this site”- C# for beginners: the language from zero, written at the same time as this course.
- Spring Boot, Spring Cloud and Reactor for C# developers: the same questions from the Java side, with the same running example.
- Music theory for Guitar Alchemist reads the same GA projects for what they compute; this course reads them for how they run.
- Rust for C#/Java developers makes explicit what .NET decides for you: ownership instead of a garbage collector, borrowing instead of
refsafety rules.
Resources
Section titled “Resources”- .NET fundamentals: memory management and garbage collection, and the GC configuration settings.
- C# language reference, in particular ref structs and asynchronous programming.
- Stephen Toub, Performance Improvements in .NET 10 and How async/await really works in C#, on the .NET blog.
- The dotnet/runtime repository: the lessons link the lines of the runtime they rely on, at tag
v10.0.12(commit4271d88). - BenchmarkDotNet and its good practices.
- Microsoft Learn: channels, TPL Dataflow, ASP.NET Core fundamentals.
- Ian Griffiths and Lee Campbell, Introduction to Rx.NET, 2nd edition, free online.
- Konrad Kokosa, Pro .NET Memory Management (Apress, 2018): older than .NET 10, still the deepest book on the GC.