Every new .NET release brings a substantial number of improvements: performance optimizations, language features, new APIs, and changes to existing behavior.

The challenge isn't keeping up with every announcement. It's understanding which changes actually make a difference when developing and maintaining applications.

.NET 11 and C# 15, scheduled for general availability in November 2026, continue this evolution. Highlights include union types, closed hierarchies, a new asynchronous execution model, improvements in SQL generation, and better integration with observability tools.

Some of these features can significantly simplify our code. Others work behind the scenes, reducing execution overhead or addressing behaviors that have been difficult to work with.

There are also changes that require careful attention, particularly for applications using Entity Framework Core, SQL Server, Cosmos DB, or background services.

In this article, I'd like to explore what I consider the most relevant developments, explaining not just what has changed, but why these changes matter and how we can evaluate them in real-world projects.

C# 15: A More Expressive Language

One of the things I've always appreciated about C# is its ability to evolve without abandoning the principles that made it popular: strong typing, readability, and a consistent ecosystem.

C# 15 continues in that direction. Some additions are relatively small syntax improvements, while others can directly influence how we model our domains.

Union Types: Making Possibilities Explicit

Imagine a payment operation that can end in three states: approved, declined, or pending.

A traditional implementation might use a class containing a status property, several optional properties, and conditional checks to determine which information is available.

That works, but there's a problem: the model may allow combinations of properties that don't make sense.

With union types, we can explicitly represent a result that belongs to a known set of possible types.

In C# 15, we can declare a union using the union keyword.

For example, assuming three types have already been defined, PaymentApproved, PaymentDeclined, and PaymentPending, we can write:

public union PaymentResult(PaymentApproved, PaymentDeclined, PaymentPending);

The compiler understands the types that make up the union and can verify whether a switch expression handles every possible case.

This is particularly interesting in domain-oriented applications.

Instead of representing results through combinations of Boolean values, strings, and optional properties, we can express different outcomes directly through the type system.

The benefit isn't simply writing less code. It's making the model more explicit and allowing the compiler to catch certain errors before execution.

Of course, union types don't replace domain modeling. Entities, value objects, and aggregates still have their own responsibilities. This is another tool for accurately representing states and outcomes.

Closed Hierarchies: Greater Control Over Inheritance

Another interesting addition is the closed modifier.

It allows us to declare that a class can only have direct descendants defined within the same assembly.

As a result, the compiler can identify the types directly derived from that class and verify pattern coverage in certain switch expressions.

Consider a set of operation states: created, processing, and completed.

When those states belong to a controlled hierarchy, we can prevent new directly derived types from being introduced freely by other assemblies.

There's an important distinction: the restriction isn't automatically transitive. If a derived class isn't closed, it may still allow external inheritance.

In systems built around Domain-Driven Design (DDD), this feature can be useful when representing concepts with a clearly defined set of specializations.

Other Language Improvements

C# 15 also introduces arguments in collection expressions, making it possible to specify construction parameters such as initial capacity and comparers using with(...).

Extension indexers allow us to add index-based access syntax to existing types without modifying their original implementations.

Labeled break and continue statements make nested loops easier to control, avoiding some approaches that previously required auxiliary variables or goto.

These are relatively small improvements individually, but they reinforce an ongoing trend: making code more declarative without sacrificing clarity about its behavior.

Runtime Async: An Important Change to Asynchronous Execution

Among the .NET 11 features, Runtime Async deserves particular attention.

To understand its purpose, we first need to revisit how asynchronous methods traditionally work in C#.

When we write a method using async and await, the compiler typically transforms that code into a state machine.

This structure manages suspension points, preserves the necessary state, and allows execution to resume when the awaited operation completes.

It's an efficient model, but it comes with costs associated with generated code, state management, and continuation execution.

Runtime Async introduces a different approach.

Instead of relying on traditional compiler-generated state machines, the runtime itself participates directly in managing execution suspension and resumption.

What Does This Mean in Practice?

The first potential benefit is reduced overhead in asynchronous operations.

In applications that execute thousands of concurrent operations, small optimizations can accumulate and produce meaningful results.

Another benefit is the possibility of simpler stack traces.

Anyone who has investigated an exception across multiple asynchronous calls knows that understanding the original execution flow isn't always straightforward.

Runtime Async aims to make that diagnostic process more direct.

However, we should avoid jumping to conclusions.

It doesn't mean every asynchronous application will automatically become faster, or that every stack trace will shrink by a specific amount.

Results depend on the workload, operations being performed, and characteristics of the code.

It's also important to remember that Runtime Async remains an experimental feature in .NET 11.

Although parts of the framework itself are already compiled using this model, enabling it for application code requires an explicit project configuration using runtime-async=on.

My recommendation is to evaluate it first in experimental projects, running performance, stability, and compatibility tests before considering it for critical systems.

JIT Improvements: Better Performance Without Necessarily Changing Your Code

Not every performance improvement requires application changes.

The Just-in-Time (JIT) compiler, responsible for translating intermediate language into executable machine instructions, has also received optimizations.

These include eliminating redundant bounds checks, improving devirtualization, and simplifying certain operations.

A bounds check, for example, ensures that accessing an array element doesn't exceed its valid range.

That protection is essential, but the compiler may identify situations where an additional check is unnecessary.

When that happens, it can eliminate redundant work without compromising safety.

Devirtualization follows a similar principle: when the JIT can safely determine which implementation will be called, it may optimize virtual dispatch and, in some cases, enable additional optimizations.

These improvements are particularly valuable because existing applications may benefit from them.

Still, performance needs to be measured.

Before attributing improvements exclusively to a new runtime version, it's worth using tools such as BenchmarkDotNet and examining actual CPU usage, memory consumption, latency, and throughput.

An isolated benchmark doesn't necessarily represent the behavior of an entire application.

ASP.NET Core 11: Less Repetitive Infrastructure Code

ASP.NET Core continues evolving in a direction I consider positive: providing common infrastructure capabilities without requiring every team to build similar solutions independently.

Asynchronous Validation in Minimal APIs

Previously, validation operations that depended on external queries often required pipeline customization or checks directly inside endpoints.

ASP.NET Core 11 expands support for asynchronous validation, allowing rules to query databases or external services without blocking threads while waiting.

For example, this can be useful when checking whether an identifier is already registered.

However, there's an important architectural distinction.

Being able to perform asynchronous validation doesn't mean every validation rule should depend on external infrastructure.

Simple checks, such as email formatting and required fields, still belong naturally at the application boundary.

Rules involving domain invariants should remain close to the domain model responsible for enforcing them.

Also, checking a database for uniqueness before inserting a record doesn't eliminate the need for a database constraint. Two concurrent requests may still pass the validation.

OpenTelemetry and Observability

ASP.NET Core now adds OpenTelemetry semantic attributes directly to HTTP activities.

This reduces the need for a dedicated instrumentation package to obtain certain request-related information.

It doesn't mean the entire observability infrastructure is automatically configured. We still need to define collection, export, sampling, and trace correlation.

In distributed systems, this information is essential for understanding the path a request takes across multiple services.

The improvement brings the framework closer to observability standards already widely adopted across the industry.

OpenAPI 3.2 and AI Agent Integration

Another development is the use of OpenAPI 3.2 as the default specification version for generated documents.

Among its benefits are more accurate descriptions of responses based on Server-Sent Events (SSE).

For APIs that continuously stream events, more expressive contracts help tools and consumers understand the data format.

The addition of the mcpserver template to the SDK is also worth highlighting.

Using dotnet new mcpserver, we can create the initial structure of a server based on the Model Context Protocol (MCP).

MCP makes it easier to expose tools and capabilities to AI-powered applications and agents in a controlled manner.

This creates interesting opportunities to integrate existing systems with new automation interfaces, provided that authentication, authorization, and data exposure are carefully addressed.

Entity Framework Core 11: Better Queries and More Flexible Modeling

Entity Framework Core 11 introduces relevant improvements in both LINQ translation and data mapping.

One area involves queries using Include.

Depending on entity relationships, previous versions could generate unnecessary joins and ordering operations, particularly in split queries using AsSplitQuery().

EF Core 11 can eliminate some of those operations.

In a specific benchmark published by the EF Core team, this optimization resulted in approximately a 29% performance improvement.

That's an interesting result, but it needs to be interpreted correctly: it isn't a promise that every query or application will become 29% faster.

FullJoin, MaxBy, and MinBy

The new LINQ FullJoin operator allows us to represent full outer joins between data sets.

A full outer join preserves records from both sides, even when no matching record exists.

This can be useful for reconciliation processes, comparing data sources, and generating reports that need to identify records present in only one source.

EF Core 11 can translate this operator into FULL JOIN on compatible relational database providers.

Translation support has also been expanded for operations such as MaxBy and MinBy.

These operations identify the element associated with the highest or lowest value of a specified criterion without requiring the entire collection to be transferred to the application.

The advantage is executing the operation directly in the database when a compatible translation exists.

Complex Types and DDD

Another improvement I consider particularly relevant is expanded support for complex types.

EF Core 11 allows them to be used in inheritance scenarios involving TPT and TPC strategies, while offering greater flexibility when configuring properties.

For developers working with Domain-Driven Design, this can make value object mapping easier.

An address, for example, can be represented as a dedicated type instead of spreading properties such as street, city, and postal code across an entity.

EF Core can map that structure without requiring the domain model to be designed entirely around database tables.

This doesn't eliminate the need to understand persistence, but it helps reduce coupling between domain modeling decisions and ORM limitations.

Vector and Hybrid Search

EF Core 11 also expands the possibilities for working with vector data.

In semantic search applications, embeddings represent information as numerical structures that can be compared using similarity calculations.

Integration with SQL Server vector capabilities makes it easier to build queries that combine semantic similarity with traditional text search.

This approach is particularly interesting for applications using information retrieval to support AI systems.

However, some APIs remain experimental and depend on the capabilities available in the database version being used.

Before adopting them, we need to evaluate infrastructure requirements, result quality, and query behavior.

Libraries and Tools: Small Improvements That Make Everyday Work Easier

Beyond the headline features, .NET 11 introduces practical improvements to its libraries and SDK.

The Process.RunAndCaptureTextAsync API, for example, simplifies running external processes and capturing their results.

JSON Lines support also makes incremental processing easier in scenarios where each line represents an independent JSON document.

This can be useful for data pipelines, integrations, and streaming large volumes of information.

In the SDK, the #:include directive allows file-based applications to organize code across multiple C# files without initially requiring a conventional project structure.

I don't consider this a replacement for well-structured solutions, but it can be useful for scripts, internal tools, and small automation tasks.

EF Core 11 also improves migration tooling, including the ability to create and apply a migration in a single operation and centralize options in .config/dotnet-ef.json.

Individually, these may seem like minor changes, but they can reduce repetitive development tasks.

Breaking Changes: Where Upgrading Requires Extra Care

So far, we've focused mostly on benefits. However, upgrading a platform can also change important behaviors.

And some .NET 11 changes deserve special attention.

BackgroundService and Exception Propagation

In .NET 11, when a BackgroundService fails and the host is configured with BackgroundServiceExceptionBehavior.StopHost, host execution and shutdown methods propagate that failure.

Previously, the process could terminate with an exit code indicating success even after a background service had failed.

The new behavior makes these failures more visible.

For applications running in containers or orchestrated environments, this can make abnormal termination easier to detect.

On the other hand, it's important to review scripts, monitoring systems, and restart policies that depend on the process exit code.

SQL Server Compatibility

EF Core 11 now assumes compatibility level 160, associated with SQL Server 2022.

This enables the use of newer SQL capabilities, but it may generate queries that are incompatible with older database versions or databases configured with lower compatibility levels.

Therefore, upgrading Entity Framework isn't just about verifying that the C# code still compiles.

We also need to consider how the generated SQL behaves.

Cosmos DB and the Risk of Data Loss

This is one of the changes I consider most important.

The EF Core provider for Cosmos DB now uses System.Text.Json instead of Newtonsoft.Json.

With this change, JSON properties that aren't mapped in the model are no longer automatically preserved.

Imagine a document containing fields used by another application but unknown to the current EF Core model.

When that document is loaded and saved again, those additional fields may be lost.

This isn't merely an API compatibility issue. It represents a real risk to data integrity.

Microsoft recommends explicitly mapping the required properties or using CosmosClient directly when full control over the JSON document structure is necessary.

The provider also stops supporting synchronous input/output operations.

Additional Considerations

.NET 11 raises the minimum instruction-set requirement for x86/x64 processors to x86-64-v2.

It also introduces additional integrity checks when reading ZIP and TAR files, potentially resulting in exceptions for invalid archives.

These changes reinforce an important principle: before upgrading, we need to understand both our infrastructure and the behaviors our application depends on.

Is Upgrading to .NET 11 Worth It?

This may be the most important question of all.

.NET 11 follows the STS support lifecycle, with approximately two years of support, expected through November 2028.

.NET 10, on the other hand, is an LTS release with official support through November 14, 2028.

Therefore, teams already using .NET 10 don't need to upgrade immediately just because a newer version is available.

In enterprise systems, I prefer evaluating upgrades through three dimensions: technical necessity, operational risk, and measurable benefits.

If a feature solves a concrete problem, reduces complexity, or provides meaningful performance improvements, there's a good reason to investigate it.

On the other hand, when an application is stable, depends on libraries that aren't yet compatible, or operates under significant constraints, rushing an upgrade may introduce unnecessary risks.

A reasonable strategy is to start with less critical applications, run regression tests, compare metrics, and review breaking changes before moving to core systems.

It's also worth remembering that we don't necessarily need to adopt every new language feature.

A runtime upgrade can provide benefits without requiring a domain model rewrite. Similarly, new C# features can be introduced gradually as their usefulness becomes clear.

And there's an important distinction between upgrading technology and improving architecture.

An application with excessive coupling, poorly distributed responsibilities, or inadequate observability won't have those problems solved simply because it's running on a newer .NET version.

Final Thoughts

.NET 11 and C# 15 represent another step forward for an ecosystem that continues investing in performance, productivity, and integration capabilities.

Among the new features, I find union types, closed hierarchies, Runtime Async, and Entity Framework Core's query translation improvements particularly relevant.

The additions to ASP.NET Core also demonstrate increasing attention to observability, API contracts, and integration with modern tools.

But the most important takeaway isn't knowing every available feature.

It's understanding which problems these features solve, what limitations they have, and how they can contribute to applications that are easier to maintain, observe, and evolve.

As developers and software architects, we need to keep learning without turning every release into a race to adopt the latest technology.

The best technical decision isn't necessarily to use the newest feature. It's to choose the one that solves the right problem at a cost and risk level that make sense.

Key Takeaways

  • C# 15: Union types and closed hierarchies offer new ways to model states and outcomes with stronger type safety.
  • Runtime: JIT improvements may benefit existing applications, while Runtime Async is promising but remains experimental.
  • ASP.NET Core 11: Asynchronous validation, OpenTelemetry, OpenAPI 3.2, and MCP support expand the platform's capabilities.
  • EF Core 11: More efficient queries, new LINQ operators, and vector search capabilities create opportunities, but Cosmos DB changes require particular care.
  • Upgrades: Compatibility, testing, observability, and measurable benefits should guide the decision.

Further Reading

Microsoft's official documentation is the best place to start. I recommend beginning with language and runtime changes, then exploring the areas most relevant to your work.

What's New in C# 15

https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-15

Covers language changes, including examples of union types, closed hierarchies, and extension indexers.

What's New in the .NET 11 Runtime

https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-11/runtime

Explains Runtime Async and the main performance optimizations.

What's New in ASP.NET Core 11

https://learn.microsoft.com/en-us/aspnet/core/release-notes/aspnetcore-11

Documents new validation, OpenAPI, observability, and MCP integration capabilities.

What's New in Entity Framework Core 11

https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-11.0/whatsnew

A useful reference for LINQ translation, complex types, migrations, and vector search.

Breaking Changes in EF Core 11

https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-11.0/breaking-changes

Essential reading before planning upgrades for existing systems.

.NET Support Policy

https://dotnet.microsoft.com/en-us/platform/support/policy/dotnet-core

Provides information about LTS and STS release cycles and official support dates.