Notes
Every note, kept under its topic and in the order of its trail. Corrections are dated inside each note, not hidden.
Start here
What’s a note?
The smallest unit here: one idea, written down, on its own.
What’s a trail?
Notes, in an order that means something.
What’s cequest?
CE Quest: Computer Engineering Quest, and how this site came to be.
C# 60
C# building blocks
Delegates
A type-safe function pointer, so a method can be passed around like any other value.
From delegates to callbacks, events, LINQ and async
Callbacks, events, LINQ and async/await are delegates with something added.
Events
How a class tells other classes that something happened, without knowing who is listening.
EventArgs and event data
The base class for the data an event carries.
Lambda expressions
A short way to write a method inline: an anonymous function.
Generics
Type parameters as placeholders, so one piece of code works for many types and stays type-safe.
Generics at compile time and at runtime
Generic code is type-checked by the compiler, and the types stay real at runtime.
Extension methods
Add methods to a type you can't change, without touching its source or subclassing it.
Extension methods: custom types, value types and using
Which types can be extended, how structs behave, and when an extension method is visible.
Attributes and reflection
Attributes tag code with metadata, and reflection reads that metadata back at runtime.
Source generators versus reflection
A source generator does at build time what reflection does at runtime.
C# types: class, struct, interface and record
Value types and reference types
A value type variable holds the value. A reference type variable holds a reference to an object.
Class versus struct
A struct is a value type and a class is a reference type.
Records
A class for immutable data, with value-based equality generated for you.
Records: the () way and the {} way
A record can be declared with its data in parentheses or in a body.
Init-only setters
A property you can set while creating the object, and never again.
Choosing a type
Class, struct, interface, record class or record struct.
Modern C#
Pattern matching
Test the shape of a value and pull data out of it in one step.
Relational, logical and list patterns
Compare with operators, combine patterns, and match the elements of a list.
Switch expressions
A switch that returns a value.
Nullable reference types
Reference types are non-nullable by default, and the compiler warns when that might not hold.
Other modern C# features
Seven smaller features that cut boilerplate, and a table of all of them.
Object-oriented programming in C#
Encapsulation
Keep an object's data private, and let the outside world reach it only through members you choose.
Abstraction
Expose what an object does, and hide how it does it.
Inheritance
A class that starts with everything another class has, and adds its own.
Polymorphism
One call, many behaviors, decided by the actual type at runtime.
Method hiding with new
Same name in a derived class, but not an override.
Interfaces versus abstract classes
Two ways to define what a type must offer, and when each one fits.
SOLID principles in C#
SOLID at a glance
Five design guidelines for object-oriented code, and what each one buys you.
Single Responsibility Principle
A class should have only one reason to change.
Open/Closed Principle
Open for extension, closed for modification.
Liskov Substitution Principle
A subtype must be usable anywhere its base type is.
Interface Segregation Principle
Many small interfaces beat one big one.
Dependency Inversion Principle
High-level code should depend on abstractions, not on concrete low-level classes.
Design patterns in C#
What design patterns are
Reusable solutions to common design problems, and the nine you will meet most in C#.
Creational patterns
Four patterns for creating objects in a way that suits the situation.
Structural patterns
Four patterns for composing classes and objects into larger structures.
Behavioral patterns
Four patterns for how objects interact and share out responsibilities.
Repository pattern
A collection-like interface over data access, and when it is worth adding.
C# collections and Big O
Big O notation
How run time or memory grows as the input grows.
Arrays, lists and linked lists
Three ways to keep items in order, and what each one costs.
Dictionary and HashSet
Two hash-based collections: one maps keys to values, the other keeps unique items.
Stack and Queue
Last in, first out and first in, first out.
Choosing a collection
Add, remove, access and search costs for the common collections, side by side.
Sorting and searching
What the common algorithms cost, and when each one fits.
LINQ in C#
LINQ: query syntax and method syntax
One SQL-like way to query many kinds of data, written in two equivalent forms.
Deferred execution
A LINQ query does not run when you write it. It runs when you enumerate it.
ToList and what it returns
ToList runs the query and returns a List, which offers more than IEnumerable.
LINQ providers: IQueryable and IEnumerable
Where a LINQ query actually runs depends on its provider, and on IQueryable versus IEnumerable.
Async in C#
Processes, threads and context switches
Cores run threads, and the OS swaps threads on and off them.
The thread pool
A set of reusable worker threads that take jobs from a queue.
Async and await
Await frees the thread while an operation waits, and the method picks up where it left off.
How await works under the hood
The compiler turns an async method into a state machine, and the thread is freed while it waits.
Task.Run: when it helps and when it doesn't
Task.Run moves CPU work to a pool thread. It pays off when the current thread must stay free.
I/O-bound or CPU-bound?
Async helps most when the work is waiting, and CPU-heavy work needs Task.Run.
Is this library method CPU-bound or I/O-bound?
The Async suffix does not say. Six checks, in order of effort.
Task combinators
Wait for all of several tasks, or just the first one to finish.
Thread.Sleep versus Task.Delay
Sleep blocks the thread. Delay frees it.
Async pitfalls
Four things to watch for when writing async code.
Why async void is dangerous
It discards the Task, so the caller cannot await it or catch its exceptions.
.NET and ASP.NET Core 44
.NET platform basics
The CLI, the CLR and compilation
What .NET is built on, and how C# source becomes running code.
The BCL and CoreLib
The libraries every .NET program stands on, and the one assembly the runtime cannot start without.
Streams
Reading and writing data as a flow of bytes, a chunk at a time.
The .NET SDK and the dotnet CLI
The tools you use to create, build, test and publish .NET applications.
Project structure and assemblies
What a .NET solution is made of on disk, and what the build produces.
.NET memory management
Garbage collection and generations
The runtime frees memory for you, sorted by how long objects live.
IDisposable and the Dispose pattern
How to release the resources the garbage collector does not know about.
Finalizers
A fail-safe for when Dispose was never called, and why you should rarely write one.
Efficient memory usage in .NET
Seven habits that keep allocations and leaks down.
.NET threads and the thread pool
Processes, threads and context switches
Cores run threads, and the OS swaps threads on and off them.
The thread pool
A set of reusable worker threads that take jobs from a queue.
How many threads handle one request?
One at a time, but not always the same one.
The UI thread and SynchronizationContext
One thread owns the controls, and other threads post work to it.
Node's event loop compared with the .NET thread pool
Node runs your code on one thread. .NET runs it on many.
Concurrency in .NET
Concurrency versus parallelism
Dealing with many things at once, and doing many things at once.
The tools .NET gives you for concurrency and parallelism
Threads, tasks, Parallel loops, PLINQ and async/await, and what each is for.
How many jobs can Parallel.For run?
No limit on how many you submit, a real limit on how many run at once.
Fan-out and fan-in with Parallel.For
Render independent items on all cores, keep the results in order, then merge them one at a time.
Load everything then render, or stream through a channel
Two shapes for a job that loads data (I/O) and renders it (CPU).
Heavy work inside an HTTP request
Cores are shared by every request, so one request that takes them all slows the others.
Synchronization primitives and concurrent collections
What to use when several threads touch the same data.
Where concurrent code goes wrong
Race conditions, deadlocks and livelocks.
Race conditions
The result depends on the order in which threads happen to run.
Deadlocks
Two or more threads wait on each other forever.
Livelock and starvation
Threads that keep running without progress, and threads that never get a turn.
ASP.NET Core fundamentals
Kestrel
The web server inside every ASP.NET Core application.
The middleware pipeline
How a request passes through a chain of components, and why their order matters.
Dependency injection and service lifetimes
How ASP.NET Core hands each class what it needs, and how long each instance lives.
DI scopes, threads and processes
A scope is a logical unit of work, not a thread or a process.
Configuration and the options pattern
Reading settings from layered sources, and binding them to typed classes.
Routing
How an incoming URL finds the code that handles it.
The host and WebApplicationBuilder
What starts an ASP.NET Core application and keeps it running.
Web APIs in ASP.NET Core
API controllers and MVC controllers
Two kinds of controller, and the attribute that makes an API controller behave like one.
RESTful API design
Naming, methods, status codes, formats and security for an API that is easy to use.
API versioning
Four ways to let old clients keep working while the API changes.
OpenAPI and Swagger
Describing an API in a standard format, and getting interactive documentation from it.
Data access with EF Core
ORMs, DbContext and entities
What EF Core is, and the four ideas its API is built on.
Querying with LINQ to Entities
Writing LINQ against a DbSet, and letting the provider turn it into SQL.
Loading related data
Eager, explicit and lazy loading, and the N+1 problem that lazy loading can cause.
Migrations
Evolving the database schema as the model changes, without losing the data.
Change tracking and SaveChanges
How EF Core notices what you changed, and turns it into SQL.
EF Core query habits
Six habits that keep EF Core queries fast.
Pooling, compiled queries and compiled models
Four ways to cut overhead in EF Core, and two common mix-ups between them.
EF Core performance techniques at a glance
Each technique, when to use it, and the line of code that turns it on.
Event-driven architecture 31
Event-driven foundations
Why event-driven
A service announces what happened instead of calling every service that needs to know.
Events, commands and queries
Three kinds of message: an event states a fact, a command asks one owner to act, a query only reads.
Anatomy of an event
An envelope that says which event it is and when it happened, and a payload that carries the facts.
Queue vs topic
A queue gives each message to one consumer, a topic gives every subscriber its own copy.
Broker vs event stream
A broker hands messages out and removes them once acknowledged, a stream keeps a log that consumers read at their own pace.
Partitions, offsets and consumer groups
How a stream splits a topic into lanes, marks each reader's place, and shares the work among a group.
One tool, both shapes
Kafka and RabbitMQ can each act as a work queue or as publish-subscribe, depending on how consumers attach.
Routing and topology
How a messaging system decides where each message goes: exchange types, subscription filters and partition keys.
Reliable messaging
Eventual consistency
After a change, other services catch up later, and the design has to allow for the gap.
Delivery guarantees
At-most-once, at-least-once and exactly-once: what a broker promises, and what it cannot.
Idempotent consumers
A consumer that gives the same result whether a message arrives once or five times.
Message ordering
Order exists only within one key, and only while one consumer handles that key at a time.
Retries and backoff
Run a failed message again after a growing, randomised delay, and stop after a limit.
Dead-letter queues
Where a message goes when it cannot be processed, so the rest of the traffic keeps moving.
Dead letters and ordered messages
A dead-lettered message leaves the messages after it running on state that never happened.
Outbox and inbox
Write the event in the same transaction as the data, publish it from there, and deduplicate on the way in.
Event versioning
Change an event's shape by adding to it, and run a second version beside the first when you must break it.
Event-driven patterns
Event notification vs state transfer
A thin event says that something happened. A fat event also carries the data.
Asking another service for data
Four ways to get data a service does not own, and what each costs.
Choreography vs orchestration
Services reacting to each other's events, or one coordinator telling them what to do.
Sagas
A workflow made of local transactions, with an undo step for each one.
CQRS
Separate models for changing data and for reading it.
Event sourcing
Store the events that happened, and work out the current state from them.
When to use event-driven design
Where events help, where a plain call is better, and why most systems mix both.
Event-driven in .NET
In-process events
Inside one application, a queue in memory can hand work from the request to a background worker, with no broker.
Mediator pattern in .NET
Commands and notifications dispatched to handlers inside one application, with one place that knows who handles what.
Brokers and clients for .NET
Five messaging services a .NET service can talk to, what each is for, and what a raw client leaves to you.
Messaging frameworks for .NET
What MassTransit, NServiceBus, Wolverine and CAP add on top of a raw broker client.
MassTransit, from contract to outbox
One event, OrderPlaced, carried from the Orders service to Inventory with MassTransit 8 on RabbitMQ.
Dapr pub/sub
A sidecar process that publishes and delivers messages for a service, so the code does not call the broker.
Observing event flows
Following one order through several services, and the three queue numbers that warn first.
AI agents 51
Agents from first principles
Why agentic patterns
A prompt is a tactic and breaks easily. A pattern is the strategy around it.
LLM versus agent
A model predicts text. An agent wraps that model in a loop with a goal.
Agent design principles
Four properties that make a loop an agent: goal-directed, reactive, stateful, autonomous.
State graphs
An agent drawn as nodes that read and write one shared state, joined by edges that say what runs next.
Agent memory
What an agent remembers: the thread it is in, and what it keeps across threads.
Tool use
Function calling gives the model hands: it asks, and code does the work.
Planning
Break a high-level goal into steps, store the plan, and work through it.
ReAct
Reason, act, observe, repeat: planning, tool use and a loop in one pattern.
Reflection
Critique the output before finalizing it: one agent does, another reflects.
Context engineering
Prompt engineering
Engineering the instruction given to the model.
Context engineering
Deciding what the model sees on every call, because the window is finite.
Anatomy of context
Five kinds of context, told apart by where they come from and how long they live.
The context assembler
The step that builds the prompt from scratch on every loop.
Context overflow
When the history outgrows the window: cut the oldest, or summarize them.
Context compression
Shrink what goes in, using a smaller model whose only job is to summarize.
Context reranking
Models read the middle of a long context worst, so put what matters at the edges.
Context scoping
Show the model only what it needs for this step, tools included.
Flow engineering
Design the task as a flow of steps: a model where judgement is needed, plain code everywhere else.
Graph engineering
Designing the graph itself: what lives in state, how big a node is, where it loops and where it stops.
The dynamic expert agent
Personalize with what the agent knows about the user, then assemble the right expert for the question.
Retrieval-augmented generation
Why RAG
A model only knows its training data, so the relevant data is fetched and put in front of it.
Embeddings
Data turned into an array of numbers, placed so that similar meaning sits close together.
Vector databases
Stores for embeddings that find the nearest ones fast, by trading a little accuracy for speed.
RAG ingestion
The offline half of RAG: chunk the data, embed the chunks, save them in a vector database.
RAG at query time
A one-way chain: embed the query, search, stuff the chunks into the prompt, generate.
RAG versus long context
Passing the whole file avoids retrieval, until the cost, the attention and the window limit show up.
Agentic RAG
Retrieval as a loop: the agent decides whether to search, checks what came back, and tries again.
Multi-agent patterns
The monolith agent problem
One super agent that does everything breaks the way a software monolith does.
Router pattern
A manager that only decides which specialist handles the request.
Handoff pattern
Agents in a fixed sequence, each passing its output to the next.
Group chat pattern
Agents that talk to each other in one thread, with a manager keeping order.
Swarm and map-reduce
A queen agent spawns a worker per task, then collects the results.
Choosing a pattern
Router, handoff, group chat or swarm: what each one trades away.
Agent protocols
Why agent protocols
Agents need a shared language to find each other, say what they can do, and swap context.
MCP
Model Context Protocol: one standard way for an agent to reach tools and data.
MCP lifecycle
Discovery, order, delivery: how one tool call moves between client and server.
A2A, ACP and AG-UI
The other three protocols: agent to agent, the one that merged, and agent to user.
Safety and guardrails for agents
Probabilistic risks
The model is probabilistic, so its failures are too: three threats, four things at stake.
LLM guardrails
Checks around the model, so a bad input or a bad output is caught by something that does not rely on the model behaving.
Hallucination prevention
It cannot be switched off, so it is stacked: prompt, context and flow together.
Human in the loop
A pause in the agent's decision, where a person approves before a high-risk action runs.
Ejection logic
Custom code that stops the agent, or hands over to a person, when a condition is met.
Secure tooling
Tools are where an agent touches the world, so each one is described, versioned and watched.
Agent ethics
Five things an agent is built to respect, before it is built to be clever.
Evaluating and running agents
Agent evaluation
Test the flow an agent took and what it left behind, against the goal and the guardrails.
Test datasets and mock tools
Real scenarios as a dataset, and tests that swap the tools for fakes.
LLM as judge
A second model scores the result against written criteria.
Agent ops
After deployment, watch performance, safety and cost, and whether the agent is thinking straight.
Traceability
Log the whole reasoning trace, so any answer can be explained afterwards.
Agent metrics
Six numbers that say whether a deployed agent is healthy.
The agent development lifecycle
Seven phases, from setting the goal to monitoring in production, and back.