Twenty-four years of .NET, version by version

.NET appeared in 2002 running only on Windows. For a decade it was one of the most used platforms for enterprise software, and by 2015 it had a serious problem: it was no use for what the industry was starting to do, which was deploying applications in containers on Linux servers. The answer was not to adapt what existed. It was to build a new platform in parallel and, over the years, stop developing the old one.
It is worth going through it version by version, because in each jump there is a decision that explains how we got to today’s .NET.
The Framework era (2002–2015)
1.0 (February 2002). Microsoft answers Java with a runtime of its own, the CLR (the component that runs the program and manages its memory), and a new language: C#. The proposal was that several languages could compile to the same intermediate format and run on that runtime. In practice the one that got used was C#.
2.0 (November 2005). Generics arrive: writing a list or a dictionary once that then works with any type, without losing the compiler’s checking. Java had added them a year earlier, but resolved only at compile time: the runtime does not know about them, and that imposes limitations Java still has. .NET implemented them in the runtime, with specialised code for each type. It cost more and turned out better.
3.0 (2006) and 3.5 (2007). On the same runtime come WPF, WCF and, above all, LINQ: queries written inside the language itself, with the same syntax for an in-memory list as for a table in the database. It is the platform’s most imitated feature; today almost every language has an equivalent.
4.0 (2010) and 4.5 (2012). The Task Parallel Library, and then
async/await: a syntax for writing code that waits for results — a query, a
network call — without blocking the thread of execution in the meantime, and
without chaining callbacks. C# adopted it before almost everyone. Today the same
syntax exists in JavaScript, Python, Rust and Swift.
4.8 (2019). The last version of the classic Framework. It is still supported because a lot of production software depends on it, but it will not receive new features.
By then the underlying problem was clear. The Framework was installed into the operating system: it came with Windows, it was updated with Windows, and two applications on the same server necessarily used the same version. When the industry moved to containers — images that package the application together with everything it needs to run — that way of distributing it stopped being viable.
The break (2016–2019)
.NET Core 1.0 (June 2016). A new platform written from scratch: cross-platform, open source, and with the runtime packaged alongside the application instead of installed on the machine. That lets two applications on the same server use different versions.
It also arrived with a fraction of the base library. Widely used functions were missing, a good part of the ecosystem’s libraries did not compile, and for a while you had to choose between a modern, incomplete platform and a complete one that only ran on Windows. Many teams stayed where they were, and rightly so.
2.0 (2017) and 2.1 (2018). This is where it recovers. .NET Standard 2.0
defines a common set of some twenty thousand functions that every variant must
implement, and the ecosystem compiles again. 2.1 adds Span<T>, a type that lets
you read and write over an existing fragment of memory without copying it. It
sounds minor and it is not: avoiding unnecessary copies is where almost all of
the platform’s performance improvements have come from ever since.
3.0 and 3.1 (2019). WinForms and WPF move to Core, so desktop applications
can migrate too. And nullable reference types arrive: the compiler
distinguishes between a variable that can be null and one that cannot, and
warns when the code does not account for the first case. It is enabled per
project, and it greatly reduces one of the language’s most common sources of
error.
The reunification (2020–2024)
.NET 5 (November 2020). The word “Core” disappears. One single .NET is left.
And the number 4 is skipped deliberately: .NET Framework 4.8 already existed, and calling the new version “4” would have made them impossible to tell apart in conversation or when searching documentation.
From here there is one version every November, with a fixed support scheme: even versions are LTS (long-term support, three years of security fixes), odd ones have short support. Knowing in advance when the version you use stops receiving patches changes how a migration gets planned.
6 (2021). LTS. Minimal APIs let you write an HTTP service in about twenty lines, declaring the routes directly instead of creating controller classes.
7 (2022). Native AOT (ahead-of-time): compiling straight to a native executable. Without this, .NET translates the code to machine instructions the first time each method runs, through a compiler called the JIT (just-in-time); that makes startup slower. With AOT that translation is already done. It matters most in functions that run on demand and shut down straight away, where that startup is added to the time of every call.
8 (2023). LTS. Blazor unifies its rendering models: you can decide component by component what runs on the server and what runs in the browser, instead of choosing it for the whole application.
9 (2024). Performance improvements, and the first sign of what was coming:
Microsoft.Extensions.AI appears in preview.
.NET 10, today
It shipped on 11 November 2025, it is LTS and it is supported until 10 November 2028. It is the most recent stable version.
It brings what you would expect, done well: C# 14 with extension members — you
can now add properties to somebody else’s type, not just methods — and the
field keyword for writing a property with logic without declaring the backing
variable by hand. The JIT compiler improves inlining and call resolution. On
Arm64 processors, garbage collector pauses — the moments when the program stops
to free memory — drop by between 8 and 20%, something you notice on a server
under load. ASP.NET Core adds passkeys, a password-free authentication method
based on the user’s device, and OpenAPI 3.1 becomes the default.
But what sets this version apart is something else.
Artificial intelligence enters the base library
When people talk about integrating artificial intelligence into an application, today they almost always mean language models: systems like the ones behind ChatGPT or Claude, which take a text and generate a response. They are used through an API over the internet, or by running them on your own machine.
Up to .NET 9, doing that meant picking one provider’s development kit and writing your code against it. Moving from OpenAI to a local model meant rewriting that part.
Microsoft.Extensions.AI arrives stable in .NET 10 and defines a common
interface for every provider, the same way ILogger defines one for logging
systems. The main one is IChatClient, and behind it there can be OpenAI, Azure
OpenAI, GitHub Models or Ollama running on your own machine.
// The rest of the code does not know which provider is behind it.
services.AddChatClient(new OllamaChatClient(new Uri("http://localhost:11434")))
.UseFunctionInvocation() // the model can call your methods
.UseDistributedCache() // repeated answers are not requested twice
.UseOpenTelemetry(); // and every call is recorded
It is the same layered composition that ASP.NET Core uses to process requests:
each Use… wraps the previous one and adds a responsibility. The model being
able to invoke the program’s methods, responses being cached and everything being
instrumented are not features of the provider, but layers added by the
programmer.
There are two more pieces around it. The Microsoft Agent Framework, for coordinating several of these components working on the same task, in sequence or in parallel. And support for MCP (Model Context Protocol), a protocol that standardises how a model reaches external tools: querying a database, reading a file, calling an API.
EF Core 10, for its part, adds vector search on Azure SQL and Cosmos DB. The technique consists of turning each text into a list of numbers representing its meaning — an embedding — and looking for the texts whose lists are closest to each other, instead of comparing exact words. Until now that required a separate specialised database.
What is different about this latest chapter
Generics in 2005, LINQ in 2007, async/await in 2012, Span<T> in 2018: in
all of those cases .NET designed something the rest of the industry then copied.
With artificial intelligence the position is the reverse. .NET is adopting practices that were defined first in the Python ecosystem, and it is arriving considerably later. What it contributes is the thing the platform has experience in: taking a practice each team was solving in its own way and turning it into a stable interface, with types checked by the compiler and with a record of what happens on every call.
That may be enough. IChatClient is not an original idea, but a common interface
with dependency injection, logging and caching included out of the box is exactly
what this area was missing to stop being solved with bespoke code in every
project.
What is coming
.NET 11 arrives on 10 November 2026 and is already in preview. It is a short-support version, so if you have something in production and there is no concrete reason to move, .NET 10 is where it makes sense to stay until 2028.
From what the previews show, two things look promising: union types in C# 15
— being able to declare that a value is of one type or another, and having the
compiler force you to handle both cases — and the runtime-level work on async,
which aims at improving the debugging of asynchronous code.
Comments
Sign in to comment and to like this article.
No comments yet. Be the first to write one.