Blog
Mert Özen's blog posts on software development, backend, frontend, and DevOps.
How C# async/await Works: The State Machine and Continuations
await does not always wait: on a finished task the code continues on the same thread. What the async keyword actually does, the GetAwaiter/IsCompleted/OnCompleted steps, a working hand-written state machine, looking at the compiler-generated type with reflection (a class in Debug, a struct in Release), which thread runs the continuation, and the awaitable pattern.
C# Task and Task<T>: A Promise That Starts Hot
An async method starts running the moment it is called; await does not start work, it waits for its result. The task life cycle and Status values, creating tasks with Task.FromResult and TaskCompletionSource, awaiting the same task twice, starting work first and awaiting later, and the Task<Task> trap of Task.Factory.StartNew.
C# Threads and the ThreadPool: A Short History of Async from APM and EAP to TPL
Foreground versus background threads, why the Thread class cannot carry results or errors, and a real measurement of 1000 threads against the thread pool. Then .NET's road to async: APM's Begin/End pair, EAP's Completed event, TPL's Task and the async/await that arrived with C# 5.
Why Async Exists in C#: Blocking, I/O and Scale
The same 100 stock queries finish in one second with async and in 8-9 seconds with code that blocks the thread. The difference between I/O-bound and CPU-bound work, what blocking costs in a UI and on a server, thread pool starvation, why async buys capacity rather than speed, and the fake async trap.
LINQ and .NET 10: LeftJoin, RightJoin, Shuffle, Sequence and Async LINQ
LINQ in .NET 10: LeftJoin and RightJoin replacing the GroupJoin and DefaultIfEmpty pattern, the deferred Shuffle, Sequence and InfiniteSequence built on generic math, System.Linq.AsyncEnumerable in the BCL and the CS0121 clash with the System.Linq.Async package; a short look at FullJoin coming in .NET 11.
LINQ and .NET 9: CountBy, AggregateBy and Index
LINQ in .NET 9: CountBy, which returns a sequence rather than a dictionary, AggregateBy for per-key accumulation and a BenchmarkDotNet measurement of their memory advantage over GroupBy, Index() instead of Select((x, i) => ...), the Queryable counterparts and the mutable seed trap.
LINQ and .NET 7–8: Order, ToDictionary Without Selectors and Performance
.NET 7's Order and OrderDescending, which need no key selector, why a missing IComparable only blows up at runtime, culture-sensitive versus ordinal string sorting, .NET 8's selector-less ToDictionary, and vectorized Max/Sum plus Order versus OrderBy(x => x) measured with BenchmarkDotNet.
LINQ with Real Data: Scenarios on Orders Read from a JSON File
LINQ over orders read from a file with System.Text.Json: the casing trap and JsonSerializerDefaults.Web, validating the data (duplicates, unknown customers, bad quantities), monthly revenue, best sellers, a customer report with GroupJoin, writing JSON back and streaming with DeserializeAsyncEnumerable.
LINQ Performance: Multiple Enumeration, Materializing and Measuring with BenchmarkDotNet
How many times the same query runs without you noticing, when ToList saves you and when it hurts, Any() instead of Count() > 0 and TryGetNonEnumeratedCount; measuring with BenchmarkDotNet 0.15.8 why a Where inside a loop is 50x slower than a lookup, and comparing loops with LINQ on a noisy machine, with real numbers.
Writing Your Own LINQ Operator: Extension Methods and yield return
Why a LINQ operator is just an extension method; writing lazy, streaming operators with yield return, separating argument validation from the iterator, immediate operators, finally when enumeration is abandoned early, an Expression-based operator for IQueryable and name clashes with the BCL.
LINQ Any, All, Contains, SequenceEqual and the Conversion Operators
Why All returns true on an empty sequence and why validation needs Any as well, how Any stops at the first element versus Count() > 0, Contains and record equality, SequenceEqual and the trap of records carrying collections, snapshots with ToList/ToArray/ToHashSet, duplicate keys in ToDictionary, and the difference between Cast and OfType.
LINQ Join and GroupJoin: Inner Join, Composite Keys and the Left Join Pattern
How Join, as an inner join, drops unmatched rows and multiplies multiple matches, and its documented order. The direction of equals in query syntax and CS1937/CS1938, composite keys and CS1941, hierarchical results with GroupJoin, the left join pattern with GroupJoin + DefaultIfEmpty and its null trap, multiple joins, and how Join runs in n + m with a lookup.