Torna al blog

.NET 10: Novità e Miglioramenti - La Release LTS del 2025

Scopri .NET 10: C# 14 Extension Members, Field Keyword, Minimal API Validation, File-based Apps, Performance Boost e tutte le novità della release LTS di novembre 2025.

Edoardo Midali

Edoardo Midali

Developer · Content Creator

13 min di lettura
.NET 10: Novità e Miglioramenti - La Release LTS del 2025

.NET 10 è stato rilasciato l'11 novembre 2025 ed è una release Long-Term Support (LTS) con 3 anni di supporto fino al novembre 2028. Questa versione introduce C# 14 con Extension Members, il keyword field, Minimal API Validation, file-based apps e miglioramenti significativi di performance.

🎯 Novità Principali

C# 14: Extension Members (Extension Everything)

La feature più attesa: estendi qualsiasi tipo con proprietà, metodi statici e operatori, non solo metodi!

❌ Prima - Solo extension methods:

public static class StringExtensions
{
    // Solo metodi possibili
    public static bool IsEmpty(this string value)
        => string.IsNullOrWhiteSpace(value);
}

// Uso
string text = "Hello";
bool empty = text.IsEmpty();

✅ C# 14 - Extension Members con nuova sintassi:

public static class EnumerableExtensions
{
    // Extension block - specifica il receiver una volta sola
    extension<T>(IEnumerable<T> source)
    {
        // ✅ Extension Property
        public bool IsEmpty => !source.Any();

        // ✅ Extension Method
        public IEnumerable<T> Shuffle()
        {
            var random = new Random();
            return source.OrderBy(_ => random.Next());
        }

        // ✅ Extension Indexer
        public T this[int index] => source.ElementAt(index);
    }

    // Extension statici (receiver senza nome)
    extension<T>(IEnumerable<T>)
    {
        // ✅ Static Extension Method
        public static IEnumerable<T> Range(int start, int count)
            => Enumerable.Range(start, count).Cast<T>();

        // ✅ Static Extension Property
        public static IEnumerable<T> Empty => Enumerable.Empty<T>();
    }
}

Uso naturale:

var numbers = new[] { 1, 2, 3, 4, 5 };

// Instance extension property
bool isEmpty = numbers.IsEmpty; // false

// Instance extension method
var shuffled = numbers.Shuffle();

// Instance extension indexer
int third = numbers[2]; // 3

// Static extension method
var range = IEnumerable<int>.Range(1, 10);

// Static extension property
var empty = IEnumerable<string>.Empty;

Vantaggi Extension Members:

  • Proprietà: Non più limitati ai soli metodi
  • Membri statici: Estendi anche con metodi/proprietà statiche
  • Raggruppamento: Organizza extension per receiver type
  • Meno ripetizione: Receiver dichiarato una sola volta
  • Backward compatible: Vecchia sintassi continua a funzionare

C# 14: Field Keyword

Accedi al backing field delle auto-property senza dichiararlo esplicitamente!

❌ Prima - Full property necessaria:

public class User
{
    private string _email; // Backing field esplicito

    public string Email
    {
        get => _email;
        set => _email = value ?? throw new ArgumentNullException(nameof(value));
    }
}

✅ C# 14 - Keyword field:

public class User
{
    // Backing field implicito accessibile con 'field'
    public string Email
    {
        get; // Auto getter
        set => field = value ?? throw new ArgumentNullException(nameof(value));
    }

    // Validazione con logica custom
    public string Username
    {
        get;
        set
        {
            if (value.Length < 3)
                throw new ArgumentException("Username too short");
            field = value.ToLower(); // Normalizza
        }
    }

    // Notifica cambio valore
    public int Age
    {
        get;
        set
        {
            if (field != value)
            {
                field = value;
                OnPropertyChanged(nameof(Age));
            }
        }
    }
}

Casi d'uso comuni:

public class Product
{
    // Lazy initialization
    public List<string> Tags
    {
        get => field ??= new List<string>();
        set;
    }

    // Clamping
    public decimal Price
    {
        get;
        set => field = Math.Max(0, value);
    }

    // Trim string
    public string Name
    {
        get;
        set => field = value?.Trim() ?? string.Empty;
    }
}

File-Based Apps (C# Scripting)

C# diventa un linguaggio di scripting per CLI e utility!

✅ Esegui file .cs singoli senza .csproj:

// hello.cs - File singolo eseguibile!
using System;

if (args.Length == 0)
{
    Console.WriteLine("Hello, World!");
}
else
{
    Console.WriteLine($"Hello, {args[0]}!");
}
# Esegui direttamente!
dotnet run hello.cs
# Output: Hello, World!

dotnet run hello.cs -- Mario
# Output: Hello, Mario!

Esempio: Script API Tester

// api-test.cs
using System.Net.Http;
using System.Text.Json;

var client = new HttpClient();
var response = await client.GetAsync("https://api.github.com/users/microsoft");
var json = await response.Content.ReadAsStringAsync();
var data = JsonSerializer.Deserialize<JsonElement>(json);

Console.WriteLine($"Name: {data.GetProperty("name")}");
Console.WriteLine($"Followers: {data.GetProperty("followers")}");
dotnet run api-test.cs

Vantaggi file-based apps:

  • Zero boilerplate: Nessun .csproj o namespace
  • Rapid prototyping: Testa idee velocemente
  • Automation: Script per DevOps e CI/CD
  • Compete con Python/Node: C# per scripting!

Null-Conditional Assignment Operator (?=)

Assegna solo se non null - elimina if verbosi!

❌ Prima - if null check:

public void ProcessPayment(Account? account, decimal amount)
{
    if (account != null)
    {
        account.Balance -= amount;
    }
}

✅ C# 14 - Operator ?=:

public void ProcessPayment(Account? account, decimal amount)
{
    account?.Balance -= amount; // ✨ Assegna solo se account != null
}

Esempi avanzati:

// Compound assignment
order?.Total += tax;
user?.Score *= 1.5m;
cart?.Items[0]?.Quantity--;

// Nested properties
customer?.Address?.City = "Milan";

// Arrays
users?[index]?.IsActive = true;

// Evita chiamate inutili
user?.Settings = GetExpensiveSettings(); // GetExpensiveSettings() chiamato solo se user != null

Limitazioni:

// ❌ Non supportati: ++ e --
user?.Counter++; // Errore di compilazione

// ✅ Usa += invece
user?.Counter += 1;

ASP.NET Core 10: Minimal API Validation

Built-in validation per Minimal APIs come i Controller!

✅ Setup automatico:

using Microsoft.AspNetCore.Builder;
using System.ComponentModel.DataAnnotations;

var builder = WebApplication.CreateBuilder(args);

// Abilita validation per Minimal APIs
builder.Services.AddValidation();

var app = builder.Build();

// DTO con validation attributes
public record CreateUserRequest(
    [Required] [MinLength(3)] string Username,
    [Required] [EmailAddress] string Email,
    [Range(18, 100)] int Age
);

// Endpoint - validation automatica!
app.MapPost("/users", (CreateUserRequest request) =>
{
    // Se arriviamo qui, request è VALIDO
    return TypedResults.Created($"/users/{request.Username}", request);
});

app.Run();

Validation automatica - risposta error:

POST /users
{
  "username": "ab",
  "email": "invalid-email",
  "age": 15
}
// Risposta 400 Bad Request automatica
{
  "title": "One or more validation errors occurred.",
  "status": 400,
  "errors": {
    "Username": [
      "The field Username must be a string with a minimum length of 3."
    ],
    "Email": ["The Email field is not a valid e-mail address."],
    "Age": ["The field Age must be between 18 and 100."]
  }
}

Custom validation attributes:

[AttributeUsage(AttributeTargets.Property)]
public class NotEmptyGuidAttribute : ValidationAttribute
{
    public override bool IsValid(object? value)
    {
        if (value is not Guid guid)
            return false;

        return guid != Guid.Empty;
    }
}

public record CreateOrderRequest(
    [NotEmptyGuid] Guid ProductId,
    [Range(1, 100)] int Quantity
);

Validation su parametri singoli:

// Valida anche query params e headers
app.MapGet("/products/{id}",
    ([Range(1, int.MaxValue)] int id) =>
{
    return TypedResults.Ok(new { ProductId = id });
});

Custom error responses:

// Registra IProblemDetailsService custom
builder.Services.AddSingleton<IProblemDetailsService, CustomProblemDetailsService>();

public class CustomProblemDetailsService : IProblemDetailsService
{
    public ValueTask WriteAsync(ProblemDetailsContext context)
    {
        // Personalizza formato errori
        context.ProblemDetails.Extensions["timestamp"] = DateTime.UtcNow;
        context.ProblemDetails.Extensions["traceId"] = Activity.Current?.Id;

        return ValueTask.CompletedTask;
    }
}

ASP.NET Core 10: Server-Sent Events (SSE)

API nativa per streaming eventi dal server al client!

app.MapGet("/events", () =>
{
    async IAsyncEnumerable<SseItem<string>> GenerateEvents()
    {
        for (int i = 0; i < 10; i++)
        {
            await Task.Delay(1000);
            yield return new SseItem<string>(
                data: $"Event {i}",
                eventType: "update",
                id: i.ToString()
            );
        }
    }

    return TypedResults.ServerSentEvents(GenerateEvents());
});

Client JavaScript:

const eventSource = new EventSource("/events");

eventSource.addEventListener("update", (e) => {
  console.log("Received:", e.data);
  console.log("ID:", e.lastEventId);
});

Caso d'uso: Real-time dashboard

app.MapGet("/dashboard/metrics", () =>
{
    async IAsyncEnumerable<SseItem<MetricData>> StreamMetrics()
    {
        while (true)
        {
            var metrics = await GetCurrentMetrics();

            yield return new SseItem<MetricData>(
                data: metrics,
                eventType: "metric-update"
            );

            await Task.Delay(5000); // Ogni 5 secondi
        }
    }

    return TypedResults.ServerSentEvents(StreamMetrics());
});

public record MetricData(int ActiveUsers, decimal Revenue, int RequestsPerSecond);

EF Core 10: Named Query Filters

Multiple query filters per entity con enable/disable selettivo!

❌ Prima - Un solo filter globale:

modelBuilder.Entity<Order>()
    .HasQueryFilter(o => !o.IsDeleted);
// Non puoi aggiungerne altri o disabilitarli selettivamente

✅ EF Core 10 - Named filters:

public class AppDbContext : DbContext
{
    public DbSet<Order> Orders { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // Definisci filtri nominati
        modelBuilder.Entity<Order>()
            .HasQueryFilter("SoftDelete", o => !o.IsDeleted)
            .HasQueryFilter("ActiveOnly", o => o.Status == OrderStatus.Active)
            .HasQueryFilter("CurrentYear", o => o.Date.Year == DateTime.Now.Year);
    }
}

Disabilita filtri specifici:

// Query normale - TUTTI i filtri attivi
var activeOrders = await context.Orders.ToListAsync();

// Disabilita singolo filtro
var allOrders = await context.Orders
    .IgnoreQueryFilter("SoftDelete")
    .ToListAsync();

// Disabilita multiple filtri
var allArchived = await context.Orders
    .IgnoreQueryFilters("ActiveOnly", "CurrentYear")
    .ToListAsync();

// Disabilita TUTTI i filtri
var rawData = await context.Orders
    .IgnoreQueryFilters()
    .ToListAsync();

EF Core 10: LeftJoin e RightJoin Operators

LINQ supporta finalmente LEFT/RIGHT JOIN espliciti!

// LEFT JOIN nativo
var results = from customer in context.Customers
              leftjoin order in context.Orders
                  on customer.Id equals order.CustomerId
              select new
              {
                  Customer = customer.Name,
                  OrderId = order != null ? order.Id : null
              };

// RIGHT JOIN
var results = from order in context.Orders
              rightjoin customer in context.Customers
                  on order.CustomerId equals customer.Id
              select new { Customer = customer.Name, Order = order };

EF Core 10: Complex Types JSON Support

Struct e complex types in colonne JSON!

public class Order
{
    public int Id { get; set; }

    // Struct in JSON
    public Address ShippingAddress { get; set; }
}

public struct Address
{
    public string Street { get; set; }
    public string City { get; set; }
    public string ZipCode { get; set; }
}

// Configuration
modelBuilder.Entity<Order>()
    .OwnsOne(o => o.ShippingAddress, a =>
    {
        a.ToJson(); // Salva come JSON in DB
    });

Query JSON properties:

// Filtra su proprietà JSON
var orders = await context.Orders
    .Where(o => o.ShippingAddress.City == "Milan")
    .ToListAsync();

// Update JSON column
await context.Orders
    .Where(o => o.ShippingAddress.City == "Rome")
    .ExecuteUpdateAsync(setters => setters
        .SetProperty(o => o.ShippingAddress.ZipCode, "00100"));

🚀 .NET 10 Runtime: Performance Boost

JIT Improvements

Miglioramenti nel JIT compiler includono ottimizzazioni per l'inlining, devirtualizzazione dei metodi e allocazioni stack:

  • Struct arguments: Membri passati direttamente nei registri (-20% memory overhead)
  • Loop inversion: Ottimizzazione loop più precisa (+15% throughput)
  • Array devirtualization: Enumerazioni array inline senza virtual calls

AVX10.2 Hardware Acceleration

Supporto per AVX10.2 per processori Intel/AMD più recenti:

// Vector operations auto-ottimizzate
var numbers = new int[1000];
var sum = numbers.Sum(); // Usa AVX10.2 se disponibile

NativeAOT Enhancements

Miglioramenti a NativeAOT riducono dimensioni e startup time:

  • -30% binary size per app console
  • -40% startup time per microservices
  • Migliore tree-shaking e dead code elimination

🔧 SDK & Tooling

NuGet Auto-Pruning

Package framework automaticamente rimossi se già nel runtime:

<!-- Prima -->
<ItemGroup>
    <PackageReference Include="System.Text.Json" Version="10.0.0" />
    <PackageReference Include="System.Linq" Version="10.0.0" />
</ItemGroup>

<!-- .NET 10 - Rimossi automaticamente! -->
<!-- Sono già nel runtime, riduce deps.json -->

Vantaggi:

  • ✅ Build più veloci (meno package da restore)
  • ✅ Meno false positive in security audit
  • ✅ File .deps.json più piccolo

Disabilita se necessario:

<PropertyGroup>
    <RestoreEnablePackagePruning>false</RestoreEnablePackagePruning>
</PropertyGroup>

MSBuild Task unificato

MSBuild tasks ora girano su .NET invece che .NET Framework:

# Prima - task diversi per VS e CLI
msbuild.exe  # Usa .NET Framework 4.8
dotnet build # Usa .NET

# .NET 10 - STESSO task ovunque!
msbuild.exe  # Usa .NET 10
dotnet build # Usa .NET 10

dotnet tool exec

Esegui tool senza installarli globalmente:

# Prima - installa globalmente
dotnet tool install -g dotnet-ef
dotnet ef migrations add InitialCreate

# .NET 10 - esegui al volo!
dotnet tool exec dotnet-ef migrations add InitialCreate

# Utile per CI/CD
dotnet tool exec dotnet-format --verify-no-changes

CLI Schema Inspection

# Ispeziona CLI come JSON tree
dotnet --cli-schema

# Output JSON con tutti i comandi disponibili
{
  "commands": [
    { "name": "build", "options": [...] },
    { "name": "run", "options": [...] }
  ]
}

🔐 Cryptography: Post-Quantum Support

Nuovi algoritmi crittografici post-quantum: ML-KEM, ML-DSA e SLH-DSA:

using System.Security.Cryptography;

// ML-KEM (FIPS 203) - Key Encapsulation
using var mlKem = MLKem.Create(MLKemParameterSpec.ML_KEM_768);
var (ciphertext, sharedSecret) = mlKem.Encapsulate(publicKey);

// ML-DSA (FIPS 204) - Digital Signatures
using var mlDsa = MLDsa.Create(MLDsaParameterSpec.ML_DSA_65);
byte[] signature = mlDsa.SignData(data);
bool isValid = mlDsa.VerifyData(data, signature);

// SLH-DSA (FIPS 205) - Stateless Hash-Based Signatures
using var slhDsa = SLHDsa.Create(SLHDsaParameterSpec.SLH_DSA_SHA2_128S);
byte[] hashSignature = slhDsa.SignData(message);

Perché importante?

"Harvest now, decrypt later" attack: gli attaccanti raccolgono dati cifrati oggi per decifrarli quando i computer quantistici saranno disponibili.

📊 Blazor WebAssembly Preloading

Blazor carica risorse in background per startup più veloce:

// Program.cs
builder.Services.AddBlazorWebAssemblyPreloading(options =>
{
    options.PreloadAssemblies = true;
    options.PreloadCss = true;
    options.PreloadImages = true;
});

Risultato:

  • -40% perceived load time
  • Risorse fetchate durante splash screen
  • User experience più smooth

🔄 Come Aggiornare a .NET 10

Aggiornamento Automatico

# Verifica versione attuale
dotnet --version

# Aggiorna CLI e SDK
dotnet tool update --global dotnet-sdk-10

# Aggiorna progetti
cd MyProject
dotnet update @angular/core@10 @angular/cli@10

# O manualmente
dotnet add package Microsoft.AspNetCore.App --version 10.0.0

Migration da .NET 9

<!-- Cambia TargetFramework -->
<PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
</PropertyGroup>
# Rebuild
dotnet clean
dotnet build

# Test
dotnet test

Abilita C# 14

<!-- Usa C# 14 features -->
<PropertyGroup>
    <LangVersion>14</LangVersion>
    <!-- O latest per sempre ultima versione -->
    <LangVersion>latest</LangVersion>
</PropertyGroup>

⚠️ Breaking Changes

HttpClient in Blazor WASM

HttpClient ora richiede registrazione esplicita:

// ❌ Prima - automatico
// HttpClient disponibile senza setup

// ✅ .NET 10 - registra esplicitamente
builder.Services.AddScoped(sp =>
    new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });

IWebHost Obsoleto

// ❌ Obsoleto
var host = new WebHostBuilder()
    .UseKestrel()
    .Build();

// ✅ Usa WebApplicationBuilder
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

Razor Runtime Compilation Obsoleto

Compilazione Razor runtime disabilitata - richiede precompilazione:

# Prima - editing Razor in runtime
dotnet watch run

# .NET 10 - precompilazione necessaria
dotnet build
dotnet run

📊 Performance Benchmarks

Metrica .NET 9 .NET 10 Miglioramento
API Latency (p50) 12ms 8ms -33%
Throughput (req/sec) 45K 63K +40%
Memory Allocation 2.1 GB 1.5 GB -29%
Startup Time 850ms 510ms -40%
Binary Size (AOT) 15.2 MB 10.6 MB -30%
GC Pause Time 18ms 11ms -39%

🔗 Risorse Utili

💡 Conclusioni

.NET 10 è una release LTS fondamentale:

C# 14 Extension Members - Finalmente "extension everything" ✅ Field Keyword - Semplifica auto-properties con logica custom ✅ File-based Apps - C# diventa linguaggio di scripting ✅ Minimal API Validation - Colma gap storico con Controller ✅ Performance - +40% throughput, -30% memory, -40% startup ✅ Post-Quantum Crypto - Sicurezza per i prossimi decenni ✅ EF Core 10 - Named filters, LEFT/RIGHT JOIN, JSON structs ✅ SSE Support - Real-time streaming nativo

Quando aggiornare:

  • Nuovi progetti: Parti con .NET 10 da subito
  • ⚠️ Progetti esistenti: Testa minimal API validation e C# 14
  • 📚 Enterprise: LTS garantisce 3 anni di supporto (fino a nov 2028)