DEV Community

IronSoftware
IronSoftware

Posted on

.NET 10 and C# 14 Development Tutorial

.NET 10 shipped November 2025 as an LTS release — three years of support. C# 14 brings extension members, the field keyword, and file-based apps. Here's what you need to know.

// .NET 10 — Run C# files directly, no project required
// Save as hello.cs, then: dotnet run hello.cs

Console.WriteLine("Hello from C# 14!");
Enter fullscreen mode Exit fullscreen mode

One file. No .csproj. No .sln. Just code.

What's the Biggest Change in .NET 10?

File-based apps. You can now run a single .cs file without creating a project:

# Create hello.cs with your code
# Then run it directly
dotnet run hello.cs
Enter fullscreen mode Exit fullscreen mode

Previously, even a simple "Hello World" required:

  • Solution file (.sln)
  • Project file (.csproj)
  • Source file (.cs)

Now you just need the source file. Perfect for scripts, utilities, and learning.

What Are Extension Members in C# 14?

The headline feature. Previously, you could only create extension methods. Now you can extend types with:

  • Properties
  • Methods
  • Events
  • Operators
// Extension property (NEW in C# 14)
public implicit extension StringExtensions for string
{
    public bool IsNullOrEmpty => string.IsNullOrEmpty(this);
    public int WordCount => this?.Split(' ').Length ?? 0;
}

// Usage
string text = "Hello World";
Console.WriteLine(text.WordCount);     // 2
Console.WriteLine(text.IsNullOrEmpty); // false
Enter fullscreen mode Exit fullscreen mode

Extension properties clean up code that previously required method calls:

// Old way (extension method)
if (text.IsNullOrEmpty()) { }

// New way (extension property)
if (text.IsNullOrEmpty) { }
Enter fullscreen mode Exit fullscreen mode

What Is the field Keyword?

Write property accessors without declaring backing fields:

// Old way
private string _name;
public string Name
{
    get => _name;
    set => _name = value?.Trim() ?? throw new ArgumentNullException();
}

// C# 14 way
public string Name
{
    get => field;
    set => field = value?.Trim() ?? throw new ArgumentNullException();
}
Enter fullscreen mode Exit fullscreen mode

The compiler synthesizes the backing field automatically. Cleaner code, same behavior.

What Changed with Null-Conditional Assignment?

The ?. and ?[] operators now work on the left side of assignments:

// C# 14 — null-conditional assignment
customer?.Address?.City = "New York";

// Equivalent to
if (customer?.Address != null)
{
    customer.Address.City = "New York";
}
Enter fullscreen mode Exit fullscreen mode

Compound assignments work too:

order?.Total += 100;
list?[0] = "Updated";
Enter fullscreen mode Exit fullscreen mode

How Does nameof Work with Unbound Generics?

You can now use open generic types in nameof:

// C# 14
var name = nameof(List<>);      // "List"
var dict = nameof(Dictionary<,>); // "Dictionary"

// Previously you needed a closed type
var name = nameof(List<int>);   // "List"
Enter fullscreen mode Exit fullscreen mode

Useful for reflection scenarios and attribute declarations.

What Are the New Span Conversions?

Implicit conversions between arrays, spans, and read-only spans:

int[] array = [1, 2, 3, 4, 5];

// Implicit conversions (C# 14)
Span<int> span = array;
ReadOnlySpan<int> readOnly = span;

void Process(ReadOnlySpan<int> data) { }

// All these work without explicit casts
Process(array);
Process(span);
Process(readOnly);
Enter fullscreen mode Exit fullscreen mode

Less ceremony, better performance through stack allocation.

What's New in .NET 10 Runtime?

Performance improvements:

  • JIT inlining enhancements
  • Method devirtualization improvements
  • Better stack allocations
  • AVX10.2 support
  • Enhanced NativeAOT
  • Improved struct argument handling
  • Better loop optimization

These are automatic. Your existing code runs faster.

What's New in .NET 10 Libraries?

Cryptography:

  • Post-quantum algorithms (ML-KEM, ML-DSA, SLH-DSA)
  • Future-proof encryption

JSON serialization:

var options = new JsonSerializerOptions
{
    AllowDuplicateProperties = false,  // New
    StrictMode = true                   // New
};

// PipeReader support for streaming JSON
await JsonSerializer.DeserializeAsync<Data>(pipeReader);
Enter fullscreen mode Exit fullscreen mode

Collections:

  • New OrderedDictionary<TKey, TValue>
  • Performance improvements across collections

What's New in ASP.NET Core 10?

Minimal API validation:

app.MapPost("/users", ([FromBody] User user) =>
{
    // Automatic validation
    return Results.Created($"/users/{user.Id}", user);
})
.WithValidation(); // New in .NET 10
Enter fullscreen mode Exit fullscreen mode

OpenAPI 3.1 and YAML support:

builder.Services.AddOpenApi(options =>
{
    options.OpenApiVersion = OpenApiVersion.v3_1;
    options.OutputFormat = OpenApiFormat.Yaml;
});
Enter fullscreen mode Exit fullscreen mode

Server-Sent Events (SSE):

app.MapGet("/events", async (HttpContext context) =>
{
    context.Response.Headers.ContentType = "text/event-stream";

    await foreach (var message in GetMessages())
    {
        await context.Response.WriteAsync($"data: {message}\n\n");
        await context.Response.Body.FlushAsync();
    }
});
Enter fullscreen mode Exit fullscreen mode

What's New in Entity Framework Core 10?

Complex types:

[ComplexType]
public class Address
{
    public string Street { get; set; }
    public string City { get; set; }
}

public class Customer
{
    public int Id { get; set; }
    public Address BillingAddress { get; set; }  // Stored inline
}
Enter fullscreen mode Exit fullscreen mode

Left and Right Joins:

var query = from c in context.Customers
            join o in context.Orders on c.Id equals o.CustomerId into orders
            from o in orders.DefaultIfEmpty()  // Left join
            select new { c.Name, OrderId = o?.Id };

// Or using method syntax
var leftJoin = context.Customers
    .LeftJoin(context.Orders, c => c.Id, o => o.CustomerId,
        (c, o) => new { c.Name, o.OrderId });
Enter fullscreen mode Exit fullscreen mode

Named filters:

modelBuilder.Entity<Order>()
    .HasQueryFilter(o => !o.IsDeleted, name: "SoftDelete")
    .HasQueryFilter(o => o.TenantId == tenantId, name: "Tenant");

// Disable specific filter
context.Orders.IgnoreQueryFilters("SoftDelete").ToList();
Enter fullscreen mode Exit fullscreen mode

How Do I Migrate to .NET 10?

  1. Install .NET 10 SDK:
dotnet --version  # Should show 10.0.x
Enter fullscreen mode Exit fullscreen mode
  1. Update target framework:
<PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
</PropertyGroup>
Enter fullscreen mode Exit fullscreen mode
  1. Update packages:
dotnet restore
dotnet build
Enter fullscreen mode Exit fullscreen mode
  1. Enable C# 14 features (automatic with net10.0):
<PropertyGroup>
    <LangVersion>14.0</LangVersion>
</PropertyGroup>
Enter fullscreen mode Exit fullscreen mode

Quick Reference: C# 14 Features

Feature Example
Extension members public implicit extension for string
field keyword get => field; set => field = value;
Null-conditional assignment obj?.Property = value;
Unbound generic nameof nameof(List<>)
Span conversions Implicit array to Span

Quick Reference: .NET 10 Highlights

Area Key Features
Runtime AVX10.2, NativeAOT improvements
Libraries Post-quantum crypto, JSON improvements
ASP.NET Core Validation, OpenAPI 3.1, SSE
EF Core Complex types, LeftJoin, named filters
SDK File-based apps, direct .cs execution

.NET 10 is supported until November 2028. Start using C# 14 features today.


Written by Jacob Mellor, CTO at Iron Software. Jacob created IronPDF and leads a team of 50+ engineers building .NET document processing libraries.

Top comments (0)