Javier Nogueira

Senior software engineer · AI systems (RAG, agents) · remote

October 14, 2026

.NET Simple Lazy Cache

A single-flight caller in C#: one in-flight Lazy per key, removed when the call finishes, with a newer entry left in place.

csharp dotnet concurrency

TL;DR — SingleFactoryCaller<T> shares one in-flight Lazy<Task<T>> per key. When the call finishes, that entry is removed and a later call runs the factory again. A cancellation token from one waiter does not cancel the shared factory. T is a class.

Stack — C#, netstandard2.0 library, xUnit on net10.0, Coverlet, ReportGenerator, Stryker.NET

Links — Source · Coverage report · The same idea in Java

GetOrAddAsync stores the shared call in a dictionary guarded by a lock. The method awaits that task, and the factory task, with ConfigureAwait(false). A finishing caller removes the dictionary entry only when that entry is still the call it started. A newer entry for the same key stays.

A null or empty key throws ArgumentException. A null factory throws ArgumentNullException. A factory that returns a null task throws InvalidOperationException. A caller can block on the returned task while a synchronization context does not run posted work, and the call still completes. There is no install command and no process to start. A caller uses the library.

The library targets netstandard2.0. The xUnit tests target net10.0 and run with dotnet test. Coverlet writes the Cobertura file, ReportGenerator writes the HTML report, and dotnet-stryker is the local mutation tool. GitHub Actions publishes that HTML report. The Java version of the same caller is Java Simple Lazy Cache.

Source on GitHub · Coverage report · The same idea in Java