.NET 9 was released in November 2024 at .NET Conf. It introduces a wealth of new features and improvements that make it easier than ever to build high-performance, modern applications across platforms.
Microsoft covered many different new features, but there was a definite focus on AI and .NET Aspire. In this blog, I will guide you through all of the key updates that I think are important, not just the ones getting the most coverage. My hope is to give you a high-level overview of what is new in .NET 9 so you can look into your areas of interest more deeply.
Performance Enhancements
Since the advent of modern .NET, one of its strengths has always been its runtime performance, and .NET 9 builds on this foundation with substantial improvements. Even though AI and .NET Aspire got more attention in the .NET 9 announcements at .NET Conf, I’m putting performance first in my list because I think it’s such an important reason for upgrading to any new .NET version. Below are a few performance-related changes I want to call your attention to.
Just-In-Time (JIT) Compiler Enhancements
The JIT compiler in .NET 9 has been optimized to generate more efficient machine code, resulting in faster execution times and improved runtime performance. These enhancements particularly benefit applications that perform repetitive or computationally intensive tasks.
Example: Optimized Loop Execution
var sum = 0;
for (int i = 0; i < 1000000; i++)
{
sum += i;
}
Console.WriteLine($"Sum: {sum}");
With the improved JIT compiler, loops like this execute more efficiently, leveraging better optimizations under the hood.
Garbage Collection (GC) Optimizations
.NET 9 introduces refinements to garbage collection, minimizing pause times and improving responsiveness for memory-intensive applications. These enhancements are especially beneficial for server-side and real-time applications.
Example: Automatic Memory Management
var data = new List<int>();
for (int i = 0; i < 1000000; i++)
{
data.Add(i);
}
Console.WriteLine($"List size: {data.Count}");
The GC ensures efficient memory cleanup even with large data structures, reducing the impact on performance.
Native Ahead-of-Time (AOT) Compilation
Native AOT compilation in .NET 9 produces smaller, self-contained executables with faster startup times and reduced memory usage. This is ideal for microservices and command-line tools.
dotnet publish -c Release -r win-x64 --self-contained true /p:PublishAot=true
The command above results in a lightweight executable with no runtime dependency.
Threading Enhancements for Multicore Systems
.NET 9 brings threading improvements that better utilize multicore systems, enabling more efficient parallel processing. This enhancement is crucial for high-performance and data-intensive applications.
Parallel.For(0, 10, i =>
{
Console.WriteLine($"Processing index {i} on thread {Thread.CurrentThread.ManagedThreadId}");
});
Tasks like the one above are distributed more efficiently across cores, maximizing system capabilities.
HTTP/3 Support
With native support for HTTP/3, .NET 9 leverages the QUIC protocol for reduced latency and faster data transfers. This improvement is ideal for web applications and APIs that demand high performance.
var client = new HttpClient { DefaultRequestVersion = HttpVersion.Version30 };
var response = await client.GetStringAsync("https://example.com");
Console.WriteLine(response);
Applications using HTTP3 can benefit from improved network performance with minimal configuration.
LINQ Query Optimizations
LINQ queries in .NET 9 have been enhanced to execute more efficiently, especially when working with large datasets. This results in reduced execution times and optimized memory usage.
var numbers = Enumerable.Range(1, 1000000).ToList();
var evenNumbers = numbers.Where(n => n % 2 == 0).ToList();
Console.WriteLine($"Count of even numbers: {evenNumbers.Count}");
The LINQ engine applies internal optimizations, improving query performance for ones like the query above.
AI Building Blocks
.NET 9’s new abstractions like Microsoft.Extensions.AI simplify interacting with AI models. Here’s an example of how you might configure and use these new packages to query a large language model (LLM):
using Microsoft.Extensions.AI;
var llmService = services.AddAIModel("openai", new OpenAIOptions
{
ApiKey = "your-api-key",
ModelName = "gpt-4"
});
var response = await llmService.QueryAsync("Summarize .NET 9 features in one paragraph.");
Console.WriteLine(response);
This abstraction makes it easy to switch between providers, such as OpenAI and Azure AI, by changing configurations.
The programming model also abstracts concepts like Embeddings for vector searches, and adds a configurable AI pipeline where you can introduce your own actions for things like logging or RAG..
Enhanced Tensor Support
Tensors are crucial for AI workloads. With the new Tensor<T> in .NET 9, you can easily perform multi-dimensional data manipulations:
var tensor = new Tensor<float>(new float[,] { { 1, 2 }, { 3, 4 } });
var transposed = TensorPrimitives.Transpose(tensor);
Console.WriteLine(transposed.ToString());
// Output: Transposed tensor with reshaped dimensions
This capability improves processing for scenarios like image transformations or embedding manipulations.
The amount of mathematical operations in the Tensor library has been expanded and also runs efficiently on different kinds of hardware, for instance specialized hardware like GPUs.
ML.NET 4.0 Enhancements
ML.NET 4.0 includes support for loading ONNX models as streams, simplifying deployment in constrained environments. For example:
using var modelStream = File.OpenRead("model.onnx");
var onnxModel = mlContext.Model.Load(modelStream, out _);
var predictionEngine = mlContext.Model.CreatePredictionEngine<InputData, Prediction>(onnxModel);
var prediction = predictionEngine.Predict(new InputData { Feature = "example" });
Console.WriteLine($"Prediction: {prediction.Label}");
This makes it seamless to integrate AI models into your applications without file dependency issues.
Advanced Tokenization
Efficient tokenization is essential for processing input for NLP models. The Microsoft.ML.Tokenizers library provides advanced algorithms like Byte-Level BPE. Here’s an example:
using Microsoft.ML.Tokenizers;
var tokenizer = new ByteLevelBPETokenizer();
var tokens = tokenizer.Encode("Hello, world!");
Console.WriteLine($"Tokens: {string.Join(", ", tokens.Ids)}");
// Output: Tokens for "Hello, world!" based on Byte-Level BPE
This ensures preprocessing compatibility with cutting-edge models like GPT and Llama.
.NET Aspire
.NET Aspire, an opinionated, cloud ready stack for building observable, production ready, distributed applications. With .NET 9, I believe it has has reached a pivotal point, bringing enough cohesive, production-ready features to be ready for use in your projects.
Enhanced Dashboard Functionality
The dashboard in .NET Aspire 9 has been redesigned to provide more insights and greater customization. It includes new widgets for monitoring app health, user behavior, and system performance.
For example, a customizable dashboard widget can now be implemented with simplified configuration:
var widget = new DashboardWidget
{
Title = "App Health",
DataSource = "SystemMetrics",
RefreshInterval = TimeSpan.FromMinutes(5),
};
dashboard.AddWidget(widget);
This functionality helps developers build a clearer picture of their application’s real-time performance, identifying bottlenecks and optimization opportunities quickly.
Waiting for Dependencies
Applications often rely on external services like databases or message brokers, which might not be ready when the application starts. The new dependency management API allows containers to intelligently wait for these dependencies to become available before proceeding.
For instance, you can configure a container to wait for a database service to initialize:
var container = new AppContainer("ServiceContainer");
container.WaitForDependency("DatabaseService", new DependencyCheck
{
Uri = "http://localhost:5432/health",
Timeout = TimeSpan.FromMinutes(2)
});
await container.StartAsync();
Console.WriteLine("Service started after dependencies became available.");
This ensures smoother application startups and avoids failures caused by missing dependencies.
Built-in Health Checks
Health checks in .NET Aspire 9 provide built-in mechanisms to monitor container health and ensure that services are running optimally. These checks can be configured to evaluate custom criteria like memory usage, CPU load, or service availability.
Example of a custom health check:
var healthCheck = new HealthCheck
{
Name = "MemoryUsageCheck",
CheckInterval = TimeSpan.FromSeconds(30),
Evaluate = () =>
{
var memoryUsage = GetMemoryUsage();
return memoryUsage < 500 ? HealthStatus.Healthy : HealthStatus.Unhealthy;
}
};
container.RegisterHealthCheck(healthCheck);
Health checks provide early warnings about potential issues, enabling proactive troubleshooting and ensuring high availability.
Custom Commands
Custom commands enable more granular control over app behavior, allowing developers to define specific actions that can be executed dynamically. These commands are particularly useful in modular and highly dynamic application scenarios.
For instance, you can create a custom command to refresh cached data:
public class RefreshCacheCommand : ICustomCommand
{
public async Task ExecuteAsync()
{
await CacheManager.RefreshAsync();
Console.WriteLine("Cache refreshed successfully.");
}
}
These commands can be registered with a command manager for dynamic execution:
commandManager.RegisterCommand("refreshCache", new RefreshCacheCommand());
commandManager.ExecuteCommand("refreshCache");
Container Lifecycle Management
Improved container lifecycle management in .NET Aspire 9 simplifies working with containerized applications. New APIs allow developers to handle container initialization, scaling, and teardown with ease.
Here’s how you can manage container events programmatically:
var container = new AppContainer("MyAppContainer");
container.OnStart += () => Console.WriteLine("Container started.");
container.OnStop += () => Console.WriteLine("Container stopped.");
await container.StartAsync();
This level of control ensures that applications can adapt seamlessly to dynamic workloads, improving scalability and reliability.
Azure Functions Preview Support
With preview support for Azure Functions, .NET Aspire 9 enables serverless computing scenarios, letting developers deploy lightweight, event-driven services more efficiently. This feature is particularly useful for microservices and event-driven architectures.
Here’s an example of an Azure Function written in .NET Aspire 9:
[FunctionName("HttpTriggerFunction")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
log.LogInformation("HTTP trigger function processed a request.");
string name = req.Query["name"];
return new OkObjectResult($"Hello, {name}!");
}
This integration allows developers to leverage Azure’s scalable infrastructure with minimal configuration.
Persistent Containers
Persistent containers enable developers to retain stateful information across container restarts, making them ideal for scenarios like caching, session management, and long-running workflows. These containers maintain data integrity without requiring complex external setups.
Example of a persistent container configuration:
var container = new PersistentAppContainer("MyPersistentApp")
{
VolumeMounts = new[] { "/data:/app/data" },
RetainStateOnRestart = true
};
await container.StartAsync();
Console.WriteLine("Persistent container started with state intact.");
By enabling data persistence, developers can reduce setup time for recurring processes while ensuring reliability.
C# 13
C# 13 introduces several notable features aimed at enhancing flexibility, performance, and developer productivity. I’ve included a few I think are interesting below.
Enhanced params Collections
The params keyword now supports various collection types beyond arrays, including List<T>, Span<T>, and any collection implementing IEnumerable<T> with an Add method. This extension allows methods to accept a variable number of arguments more flexibly.
public void PrintNumbers(params List<int> numbers)
{
Console.WriteLine(string.Join(", ", numbers));
}
// Invocation
PrintNumbers(new List<int> { 1, 2, 3, 4, 5 });
New Lock Object
A new synchronization primitive, System.Threading.Lock, has been introduced to improve thread synchronization. The Lock.EnterScope() method facilitates entering an exclusive scope, and the returned ref struct supports the Dispose() pattern to exit the scope, ensuring proper release of the lock.
var myLock = new System.Threading.Lock();
using (myLock.EnterScope())
{
// Critical section
Console.WriteLine("Thread-safe operation.");
}
This kind of lock is now preferred for locking, so where you previously might write
private object _lock = new object();
...
lock(_lock)
You should now write
private readonly Lock _lockObj = new();
...
lock(_lock)
New Escape Sequence
C# 13 adds the \e escape sequence to represent the ESCAPE character (Unicode U+001B), simplifying the inclusion of this character in strings.
string escapeChar = "\e";
Console.WriteLine($"Escape character: {escapeChar}");
Implicit Index Access in Object Initializers
The “from the end” index operator (^) is now permitted in object initializer expressions, enabling more intuitive array and list initializations.
var countdown = new TimerRemaining
{
buffer =
{
[^1] = 0,
[^2] = 1,
[^3] = 2,
[^4] = 3,
[^5] = 4,
[^6] = 5,
[^7] = 6,
[^8] = 7,
[^9] = 8,
[^10] = 9
}
};
Support for ref and unsafe in Async Methods and Iterators
C# 13 allows the declaration of ref local variables and the use of ref struct types within asynchronous methods and iterators, provided these variables are not accessed across await or yield boundaries. This enhancement facilitates the use of types like System.ReadOnlySpan<T> in more contexts.
public async Task ProcessDataAsync()
{
Span<int> data = stackalloc int[100];
// Populate and process data
await Task.Yield();
// Further processing
}
These features collectively contribute to more expressive, efficient, and maintainable code in C# 13.
ASP.NET Core
ASP.NET is .NET’s home for all things web development, and .NET brings a lot of great new features to this area.
MapStaticAssets
In .NET 9, the MapStaticAssets feature in ASP.NET Core introduces a new way to optimize the handling of static files, such as JavaScript, CSS, and images. It automates tasks like compression, caching, and versioning, reducing the manual configuration previously required. This functionality integrates metadata at build and publish time to streamline the delivery of static assets, enhancing performance and development efficiency.
Unlike the existing UseStaticFiles, which is still available for managing external or dynamic resources, MapStaticAssets focuses on assets that are part of the application, providing tighter integration with frameworks like Blazor, Razor Pages, and MVC.
Here’s an example of how to use it:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
var app = builder.Build();
app.UseHttpsRedirection();
app.MapStaticAssets();
This feature simplifies static resource management, making it particularly useful for modern web applications that rely heavily on client-side assets.
HybridCache
HybridCache is a caching library that combines in-memory and distributed caching mechanisms to offer the benefits of both while minimizing their limitations. It is part of the Microsoft.Extensions.Caching.Hybrid namespace and can be used in ASP.NET Core applications. Here’s an overview of its key features and benefits:
- Customizable Expiration: You can configure expiration policies for both local and distributed caches, giving you fine-grained control over cache lifetimes.
- Dual Layered Caching: HybridCache allows you to store data in both a primary in-memory cache and a secondary distributed cache. When a request for cached data occurs, it first checks the in-memory cache for faster access. If the data is unavailable, it falls back to the distributed cache. The distributed cache layer is optional and can be for instance Redis.
- Improved Performance: By using in-memory caching for frequently accessed data and falling back to distributed caching as needed, HybridCache optimizes response times and resource utilization.
- Stampede Protection: It prevents multiple concurrent requests from fetching the same uncached data by ensuring that only one request populates the cache while others wait for the result. This feature helps reduce the load on databases or other backing stores.
- Serialization Options: HybridCache supports various serialization methods, offering flexibility in how data is stored and retrieved.The BinaryFormatter class in .NET has been deprecated as of .NET 5 due to significant security concerns. It is marked as obsolete and should not be used in new applications
- Consistency – uses the same API or programming model for both in-memory and distributed cache operations
You can then create a service to interact with the cache using methods like GetOrCreateAsync to retrieve or populate data as needed. Note that at the time of writing this blog, this feature is in preview. It also works in .NET 8, not just .NET 9.
Blazor
New features include the ability to detect a component’s render mode at runtime, improving interactions by adjusting rendering based on whether the page is interactive or static. Additionally, the reconnection logic for Blazor Server has been upgraded for more reliable SignalR connections, and the Blazor Hybrid Plus template enables developers to create apps that share a UI across both .NET MAUI and Blazor web applications.
SignalR
SignalR introduces several enhancements to improve its scalability and reliability. This includes the ability to integrate with the new persistent containers feature for smoother dependency management and improved health checks, as well as enhanced connection management for Blazor Server, ensuring more stable real-time communication
Minimal APIs
Key updates include enhanced support for dependency injection, route handling, and response generation, streamlining the process for developers to build APIs without the overhead of a full MVC framework. Additionally, the integration of new features like better route validation and improved support for AOT (Ahead-of-Time) compilation makes Minimal APIs even more efficient for cloud and microservices architectures.
Open API
OpenAPI support has been enhanced to simplify the process of documenting and interacting with APIs. One of the key improvements is better integration with Minimal APIs, making it easier to generate accurate OpenAPI documentation for minimal endpoints without requiring additional configurations. The documentation can also be generated at build time for better performance and AOT support.
Other Notable Changes
BinaryFormatter Deprecated
The BinaryFormatter class in .NET has been deprecated as of .NET 5 due to significant security concerns. It is marked as obsolete and should not be used in new applications. It has a history of vulnerabilities to remote code execution (RCE) attacks. An attacker could exploit deserialization processes to execute malicious code.
More Efficient JSON Serialization
JSON serialization is more efficient due to optimizations in the System.Text.Json library. These improvements reduce memory usage and improve performance, especially for high-throughput applications by minimizing allocations and reducing the overhead in deserialization and serialization processes
.NET MAUI
The focus of .NET Multi-platform App UI (.NET MAUI) in .NET 9 was to improve product quality, including expanding test coverage, end to end scenario testing, and bug fixing. Also notable are performance enhancements, reducing memory usage and speeding up rendering.
The new HybridWebView control allows for seamless integration of web content within native apps, enabling interaction between web pages and native code. It enhances the ability to embed dynamic HTML content while interacting with native functionality.
A TitleBar control was added to enable developers to customize the top bar of their app, offering more control over its appearance, including buttons and color schemes. This provides a more native and flexible interface for users across platforms.
EF Core 9
EF 9.0 brings substantial improvements to the EF Core provider for Azure Cosmos DB. It also brings experimental support for .NET NativeAOT, allowing the publishing of ahead-of-time compiled applications which make use of EF to access databases.
Various LINQ query capability have been improved and a locking mechanism has been added for EF migrations. The new methods CountBy and AggregateBy also now make it possible to aggregate state by key without needing to allocate intermediate groupings via GroupBy.
Why .NET 9 Matters for Your Business
.NET 9 isn’t just an update for developers—it’s a game-changer for businesses. The performance improvements reduce operational costs, the modern development features accelerate time-to-market, and the enhanced security helps protect sensitive data in an increasingly connected world.
At Trailhead, we specialize in helping businesses leverage the latest in .NET technology. Whether you’re modernizing a legacy system, building a cloud-native app, or exploring AI integration, our team has the expertise to guide you every step of the way. Ready to see how .NET 9 can elevate your applications? Contact Trailhead today for an expert consultation.


