New Features in .NET 10 & C# 14 — The Expert’s Playbook (2025)
.NET 10 (LTS) and C# 14 dropped today — November 11, 2025. As an LTS release, .NET 10 is supported through November 14, 2028. This post is your concise, code‑first tour of what's new across the stack: runtime, C#, ASP.NET Core, and EF Core 10.
Why this post?
Because this release meaningfully changes how you start small (file‑based apps), how you compose APIs (Minimal API validation + OpenAPI 3.1), and how you model data (EF Core 10 complex types & JSON). And C# 14 is packed with quality‑of‑life and performance wins.
Table of Contents
- What’s New in .NET 10
- What’s New in C# 14
- What’s New in ASP.NET Core in .NET 10
- What’s New in EF Core 10
- Other Changes in .NET 10
- Migration Notes & Practical Tips
- Summary
What’s New in .NET 10
1) File‑Based Apps (single‑file C#)
C# now behaves like a first‑class scripting language for CLIs and utilities. You can run a single *.cs file with dotnet run — no .sln or .csproj required.
dotnet run main.cs
File‑based apps support SDK and NuGet references via #: directives at the top of your file:
#:sdk Microsoft.NET.Sdk.Web
#:package Microsoft.EntityFrameworkCore.Sqlite@9.0.0
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder();
builder.Services.AddDbContext<OrderDbContext>(o => o.UseSqlite("Data Source=orders.db"));
var app = builder.Build();
app.MapGet("/orders", async (OrderDbContext db) => await db.Orders.ToListAsync());
app.Run();
return;
public record Order(string OrderNumber, decimal Amount);
public class OrderDbContext(DbContextOptions<OrderDbContext> options) : DbContext
{
public DbSet<Order> Orders => Set<Order>();
}
Reference existing projects too:
#:project ../ClassLib/ClassLib.csproj
Cross‑platform shell scripts
#!/usr/bin/env dotnet
chmod +x app.cs
./app.cs
Grow up when needed
Convert your script to a full project at any time:
dotnet project convert app.cs
Multi‑file scripting is expected to expand in future versions, but this single‑file flow already unlocks fast prototypes and ops tools.
What’s New in C# 14
C# 14 focuses on ergonomics and performance. Highlights below with paste‑ready snippets.
1) Extension Members / Extension Blocks
Group instance & static extensions (methods and properties) for a receiver in one block.
public static class StringExtensions
{
extension(string value)
{
public bool IsNullOrEmpty() => string.IsNullOrEmpty(value);
public string Truncate(int max) => string.IsNullOrEmpty(value) || value.Length <= max
? value : value[..max];
// static extension on the receiver type
public static bool IsAscii(char c) => c <= 0x7F;
}
}
2) Extension Properties
Make intent obvious and templates cleaner:
public static class EnumerableExtensions
{
extension<T>(IEnumerable<T> src)
{
public bool IsEmpty => !src.Any();
public int Count => src.Count();
}
}
3) Private fields & caching inside extension blocks
public static class CacheExtensions
{
extension<T>(IEnumerable<T> src)
{
private List<T>? _list;
public List<T> Materialized => _list ??= src.ToList();
public bool IsEmpty => Materialized.Count == 0;
}
}
4) Static extension members
public static class ProductExtensions
{
extension(Product)
{
public static Product CreateDefault() => new() { Name = "Unnamed", Price = 0 };
public static bool IsValidPrice(decimal price) => price >= 0;
}
}
5) Null‑conditional assignment
Assign with ?. without manual null checks:
user?.Profile = LoadProfile();
6) The field keyword (backing field access)
Cleaner properties without manual fields:
public class ConfigReader
{
public string FilePath
{
get => field ??= "data/config.json";
set => field = value ?? throw new ArgumentNullException(nameof(value));
}
}
7) Lambda parameter modifiers without types
delegate bool TryParse<T>(string text, out T result);
TryParse<int> parse = (text, out result) => int.TryParse(text, out result);
8) Partial constructors & partial events
Perfect for source generators:
public partial class User
{
public partial User(string name);
public partial event Action<string> Saved;
}
9) User‑defined compound assignment operators
Improve performance for in‑place ops:
public struct Money(string currency, decimal amount)
{
public decimal Amount { get; private set; } = amount;
public string Currency { get; } = currency;
public void operator +=(Money b)
{
if (Currency != b.Currency) throw new InvalidOperationException();
Amount += b.Amount;
}
}
10) nameof for unbound generics & implicit Span conversions
Console.WriteLine(nameof(List<>)); // "List"
// Many calls now infer ReadOnlySpan<T> without type noise.
What’s New in ASP.NET Core in .NET 10
1) Built‑in validation for Minimal APIs
builder.Services.AddValidation();
app.MapPost("/products",
([Range(1, int.MaxValue)] int productId, [Required] string name) =>
TypedResults.Ok(new { productId, name })
);
Disable on a route if needed:
app.MapPost("/raw", (int id, string name) => TypedResults.Ok(id))
.DisableValidation();
2) Server‑Sent Events (SSE)
Lightweight real‑time streams via TypedResults.ServerSentEvents.
public record StockPriceEvent(string Id, string Symbol, decimal Price, DateTime Timestamp);
public class StockService
{
public async IAsyncEnumerable<StockPriceEvent> Generate([EnumeratorCancellation] CancellationToken ct)
{
var symbols = new[] { "MSFT", "AAPL", "GOOG", "AMZN" };
while (!ct.IsCancellationRequested)
{
yield return new StockPriceEvent(DateTime.UtcNow:o, symbols[Random.Shared.Next(symbols.Length)],
Math.Round((decimal)(100 + Random.Shared.NextDouble()*50), 2),
DateTime.UtcNow);
await Task.Delay(TimeSpan.FromSeconds(2), ct);
}
}
}
builder.Services.AddSingleton<StockService>();
app.MapGet("/stocks", (StockService s, CancellationToken ct) =>
TypedResults.ServerSentEvents(s.Generate(ct), eventType: "stockUpdate"));
3) OpenAPI 3.1 + YAML
builder.Services.AddOpenApi(o => o.OpenApiVersion = Microsoft.OpenApi.OpenApiSpecVersion.OpenApi3_1);
if (app.Environment.IsDevelopment())
{
app.MapOpenApi("/openapi/{documentName}.yaml");
}
4) JSON Patch with System.Text.Json
dotnet add package Microsoft.AspNetCore.JsonPatch.SystemTextJson --prerelease
What’s New in EF Core 10
1) Complex Types (incl. optional & JSON mapping)
modelBuilder.Entity<Customer>(b =>
{
b.ComplexProperty(c => c.ShippingAddress);
b.ComplexProperty(c => c.BillingAddress, c => c.ToJson());
});
public class Customer
{
public int Id { get; set; }
public Address ShippingAddress { get; set; } = default!;
public Address? BillingAddress { get; set; } // optional
}
public struct Address
{
public required string Street { get; set; }
public required string City { get; set; }
public required string ZipCode { get; set; }
}
2) LeftJoin / RightJoin (LINQ operators)
var q = context.Students.LeftJoin(
context.Departments,
s => s.DepartmentID,
d => d.ID,
(s, d) => new { s.FirstName, s.LastName, Department = d.Name ?? "[NONE]" });
3) ExecuteUpdate for JSON columns
await context.Blogs.ExecuteUpdateAsync(s =>
s.SetProperty(b => b.Details.Views, b => b.Details.Views + 1));
4) Named query filters
modelBuilder.Entity<Blog>()
.HasQueryFilter("SoftDelete", b => !b.IsDeleted)
.HasQueryFilter("Tenant", b => b.TenantId == tenantId);
var all = await context.Blogs.IgnoreQueryFilters(["SoftDelete"]).ToListAsync();
5) Regular lambdas in ExecuteUpdateAsync
await context.Blogs.ExecuteUpdateAsync(s =>
{
s.SetProperty(b => b.Views, 8);
if (nameChanged) s.SetProperty(b => b.Name, "foo");
});
Other Changes in .NET 10
- Performance: broader JIT & GC improvements across runtime.
-
SDK: better
dotnetCLI UX for script→project workflows. - Libraries: incremental APIs refinements; Aspire updates.
Migration Notes & Practical Tips
-
Target frameworks: bump to
net10.0and enable C# 14 (<LangVersion>preview</LangVersion>may not be needed once toolchain is updated). -
Minimal APIs: adopt
AddValidation(); standardize 400 responses viaIProblemDetailsService. - OpenAPI: switch to 3.1; consider serving YAML for human‑readable docs.
- EF Core: start with complex types for embedded value objects and named filters for multitenancy/soft‑delete.
- Scripting: keep small CLIs as single files; convert when they need structure.
- Perf: use compound operator overloads and implicit spans in hot paths.
- Security: same TLS defaults; re‑audit auth flows if you expose SSE or file‑scripts in ops.
Summary
- .NET 10 (LTS): stable base through 2028.
-
C# 14: extension members,
field, null‑conditional assignment, partial constructors/events — less boilerplate, more clarity. - ASP.NET Core 10: Minimal API validation, OpenAPI 3.1/YAML, SSE.
- EF Core 10: complex types, JSON updates, Left/RightJoin, named filters.
If you ship APIs, CLIs, or data‑heavy apps, this release will reduce ceremony and increase velocity.
✍️ Written by: Cristian Sifuentes — .NET/C# & architecture enthusiast. If you liked this, consider subscribing to the newsletter for more deep dives and production‑ready templates.

Top comments (0)