Accueil › Nouveautés › Chapitre 22

Tout .NET 11 aperçu

Sortie prévue le 10 novembre 2026, en STS (support standard de deux ans). Ce chapitre décrit l'état des versions d'aperçu publiées au premier semestre 2026, jusqu'à la Preview 6 de juillet 2026.

À lire avant tout le reste
Le résumé en dix lignes

C# 15

Types union

public record class Succes(Commande Commande);
public record class StockInsuffisant(string Reference, int Disponible);
public record class PaiementRefuse(string Motif);

public union ResultatCommande(Succes, StockInsuffisant, PaiementRefuse);

// Conversion implicite depuis chaque cas
ResultatCommande r = new StockInsuffisant("AB-1234", 2);

// Le compilateur VÉRIFIE que tous les cas sont traités : pas besoin de « _ »
IResult reponse = r switch
{
    Succes s            => Results.Created($"/commandes/{s.Commande.Id}", s.Commande),
    StockInsuffisant si => Results.Conflict(new { si.Reference, si.Disponible }),
    PaiementRefuse pr   => Results.Problem(pr.Motif, statusCode: 402)
};

C'est la version native du motif « résultat » décrit au chapitre 9, avec une garantie que les exceptions ne donnent pas : oublier un cas ne compile pas. Le runtime fournit UnionAttribute et IUnion, et System.Text.Json sait sérialiser les unions.

Ce que union n'est pas

Rien à voir avec les unions du C, qui superposent des octets en mémoire. Il s'agit d'un type somme, comme en F# ou en Rust : une valeur qui est exactement l'un des types annoncés. La spécification n'est pas entièrement implémentée dans les aperçus actuels.

Hiérarchies fermées

public closed record class EtatPorte;                 // dérivable seulement dans cet assembly
public record class Fermee : EtatPorte;
public record class Ouverte(float Pourcentage) : EtatPorte;

string Decrire(EtatPorte e) => e switch
{
    Fermee => "fermée",
    Ouverte(var p) => $"ouverte à {p} %"
    // aucun avertissement : le compilateur connaît tous les descendants directs
};

Une classe closed est implicitement abstract et ne se combine ni avec sealed, ni avec static. La fermeture n'est pas transitive : un descendant non closed peut être dérivé ailleurs, donc marque aussi les niveaux intermédiaires si tu veux l'exhaustivité en profondeur.

Le reste de C# 15

// --- Arguments dans une expression de collection ---
string[] valeurs = ["un", "deux", "trois"];
List<string> liste = [with(capacity: valeurs.Length * 2), .. valeurs];
HashSet<string> sansCasse = [with(StringComparer.OrdinalIgnoreCase), "Bonjour", "BONJOUR"];
// → un seul élément : les deux chaînes sont égales pour ce comparateur

// --- Indexeurs d'extension ---
public static class IndexeurSequence
{
    extension(IEnumerable<int> sequence)
    {
        public int this[int index] => sequence.ElementAt(index);
    }
}
IEnumerable<int> nombres = Enumerable.Range(1, 10);
int troisieme = nombres[2];              // indexation sur un IEnumerable

// --- break / continue étiquetés ---
exterieur: for (int l = 0; l < grille.Hauteur; l++)
{
    for (int c = 0; c < grille.Largeur; c++)
    {
        if (grille[l, c].Bloque) continue exterieur;
        if (grille[l, c].Arrivee) break exterieur;
    }
}
// Remplace les drapeaux booléens et les goto ; une règle d'analyse (IDE0410) les signale

// --- Sécurité mémoire, première étape ---
// Déclarer un pointeur, prendre une adresse, utiliser fixed / stackalloc / sizeof
// ne nécessite plus de contexte « unsafe » ; DÉRÉFÉRENCER, si.
int nombre = 42;
int* pointeur = &nombre;                 // autorisé hors unsafe
// Console.WriteLine(*pointeur);          // ← nécessite toujours unsafe

L'objectif de ce dernier chantier, étalé sur plusieurs versions : faire porter le mot-clé unsafe sur les opérations qui accèdent réellement à de la mémoire non gérée, plutôt que sur la simple existence d'un pointeur. Les audits de sécurité gagnent en précision.

Runtime async

L'image

Jusqu'ici, async/await était un tour de passe-passe du compilateur : il découpait ta méthode en machine à états, avec des objets et des champs générés. Désormais, le moteur sait suspendre et reprendre lui-même. Comme passer d'une boîte de vitesses bricolée à une boîte intégrée au moteur : même conduite, moins de pièces.

EffetDétail
Piles d'appels lisiblesFini les MoveNext() et les types générés illisibles dans les traces d'erreur asynchrones — le gain le plus immédiatement visible
Moins d'allocationsPlus de machine à états ni d'objet de continuation dans les cas simples
Activé par défautPlus besoin de <EnablePreviewFeatures> pour un projet net11.0 ; les bibliothèques du framework sont déjà compilées ainsi
Optimisations supplémentairesContinuations qui renoncent à capturer le contexte d'exécution quand rien ne l'utilise, version dédiée compilée par le JIT pour les méthodes synchrones renvoyant Task

Aucun changement de code requis : ton async/await reste identique. Voir le chapitre 8 pour le modèle mental, qui ne change pas.

Runtime, bibliothèques, SDK

Runtime

  • Exigences matérielles minimales relevées (jeux d'instructions x86/x64 et Arm64 plus modernes)
  • JIT : élimination de vérifications de bornes, pliage des switch, SequenceEqual replié en constante, nouvelles instructions Arm SVE2
  • Native AOT : distribution d'appels d'interface partagée — code plus petit, plus rapide sur le code très polymorphe
  • Rapport de plantage en cours de processus sur mobile (pile managée capturée avant la fin)
  • Nouvelles API SIMD de composition de voies (Zip, Unzip, Concat, CreateGeometricSequence)

Bibliothèques

  • Zstandard dans System.IO.Compression : meilleur ratio et vitesse que gzip
  • LINQ : FullJoin, surcharges de Join/GroupJoin renvoyant des tuples
  • Validation asynchrone : AsyncValidationAttribute, IAsyncValidatableObject, Validator.ValidateObjectAsync
  • Quatre nouveaux Stream sans copie : ReadOnlyMemoryStream, WritableMemoryStream, ReadOnlySequenceStream, StringStream
  • JSON : PascalCase, politique de nommage par membre, sortie NDJSON, sérialisation des unions
  • Métriques OpenTelemetry intégrées à MemoryCache
  • Process : lancement suspendu, exécution avec capture de sortie, TryGetProcessById
  • X25519DiffieHellman, Random.NextInteger<T>, EqualityComparer<T>.Create

SDK et CLI

  • dotnet test revu : --no-dependencies, motifs d'exclusion, compteurs par assembly, affichage des tests en cours
  • Applications à fichier unique : #:include pour découper, et inclusion directe d'une DLL
  • dotnet run -e CLE=valeur pour passer des variables d'environnement
  • dotnet watch : intégration Aspire, reprise après plantage, choix de l'appareil pour MAUI
  • Installateurs Linux/macOS plus petits, entrée CLI en Native AOT
  • Images multi-architectures avec Podman, filtres de solution (.slnf) pilotables en CLI
  • Télémétrie CLI passée à OpenTelemetry

Modèle de serveur MCP

Le SDK livre un modèle de projet pour créer un serveur MCP (Model Context Protocol) en C# : la façon standardisée d'exposer des outils à un assistant IA. Signe des temps, et point d'entrée simple si tu veux connecter ton métier à un agent.

ASP.NET Core 11

Validation asynchrone

public sealed class EmailUniqueAttribute : AsyncValidationAttribute
{
    // La version synchrone est abstraite : on l'interdit explicitement
    protected override ValidationResult? IsValid(object? valeur, ValidationContext ctx) =>
        throw new InvalidOperationException("Utiliser IsValidAsync.");

    protected override async Task<ValidationResult?> IsValidAsync(
        object? valeur, ValidationContext ctx, CancellationToken ct)
    {
        var utilisateurs = ctx.GetRequiredService<IServiceUtilisateur>();
        return valeur is string email && await utilisateurs.EmailExisteAsync(email, ct)
            ? new ValidationResult("Cet email est déjà enregistré.")
            : ValidationResult.Success;
    }
}

public record Inscription([Required, EmailAddress, EmailUnique] string Email);

builder.Services.AddValidation();
app.MapPost("/inscriptions", (Inscription dto) => Results.Ok(dto));
// La vérification en base a lieu AVANT l'entrée dans ton code, sans bloquer de thread.
// Les validateurs indépendants s'exécutent en parallèle quand c'est possible.

Pour des règles portant sur plusieurs propriétés, implémente IAsyncValidatableObject et renvoie un IAsyncEnumerable<ValidationResult>. Les formulaires Blazor bénéficient du même mécanisme de bout en bout.

Protection CSRF automatique

Un middleware de protection CSRF est désormais injecté automatiquement : l'appel explicite à app.UseAntiforgery() devient optionnel dans les modèles Blazor Web App, et la validation antiforgerie du rendu serveur statique est déléguée à ce middleware. Un changement de comportement à vérifier si tu avais une configuration antiforgerie sur mesure.

Le reste

NouveautéCe que ça apporte
OpenAPI 3.2 par défautDescription des réponses binaires, du verbe HTTP QUERY, des résultats de flux de fichiers ; schémas plus fidèles au comportement réel. Changement cassant pour les outils qui n'acceptent que 3.1
Unions dans les charges JSONUn endpoint peut renvoyer une union : décrite en anyOf dans OpenAPI, sans discriminant $type. Fonctionne aussi en SignalR (protocole JSON) et en interop Blazor. Pas pour les sources non-corps (route, requête, en-têtes)
[ShortCircuit]Répondre juste après le routage, en sautant le reste du pipeline : contrôle de santé, robots.txt. Forme attribut de la convention existante, utilisable sur les contrôleurs
Traçage OpenTelemetry natifLes attributs de trace sont émis par le framework : chaque requête est tracée sans instrumentation supplémentaire
Filtres d'endpoint et échecs de liaisonLe pipeline de filtres s'exécute même quand la liaison des paramètres échoue : tu peux substituer ta propre réponse 400
SignalRRafraîchissement de l'authentification sans couper la connexion ; annulation d'une invocation depuis le client
Compression ZstandardEn réponse et en décompression de requête
DiversIOutputCachePolicyProvider, en-tête Retry-After exact du limiteur de débit, observabilité de la poignée de main TLS dans Kestrel, HTTP/3 traité plus tôt, certificats de développement approuvés automatiquement sous WSL, dotnet user-jwts pour les applications à fichier unique

Blazor 11

NouveautéDétail
Rendu statique à parité avec MVCTempData, données de session et validation côté client sans interactivité fonctionnent en SSR statique. Un formulaire classique n'a plus besoin de circuit ni de WebAssembly
Validation de formulaire asynchroneEditForm attend les validateurs asynchrones de bout en bout (recherche en base, appel d'API)
Virtualize : InitialIndex + ScrollToIndexAsyncOuvrir une longue liste directement sur un élément et y défiler à la demande. Le contenu visible ne saute plus quand la hauteur des éléments au-dessus change
Pause de circuit déclenchée par le serveurLibère les ressources d'un onglet inactif, reprise ensuite : moins de mémoire serveur en Blazor Server
Sortie WebAssembly plus légèrePublication réduite ; démarrage plus rapide côté client
Navigation relativeNavigationManager.NavigateTo et NavLink acceptent un URI relatif à l'URI courant
Composants nouveauxDisplayName (anciennement Label dans les premiers aperçus) génère un libellé accessible depuis les métadonnées ; EnvironmentView (anciennement EnvironmentBoundary) n'affiche un bloc que dans certains environnements ; BasePath
QuickGridOnRowClick, et diverses améliorations d'ergonomie
Localisation des erreursMessages de validation et d'erreur traduisibles, en Blazor comme en Minimal API
WebAssemblyIHostedService pris en charge, variables d'environnement dans la configuration, métriques et traces des composants, nouveau serveur de développement, modèle web worker, culture du serveur conservée au prérendu

EF Core 11

Les gains de performance mesurés

OptimisationGain annoncéDétail
Jointures « vers-un » inutiles supprimées en requête découpée~29 %Une requête découpée n'ajoute plus la jointure vers les navigations de référence
Clés redondantes retirées des ORDER BY~22 %La clé d'une navigation de référence est déterminée par la clé du parent : inutile de trier dessus
Propriétés vectorielles non chargées par défaut×9 en local, ×22 à distanceUn vecteur de 1 536 nombres n'est plus lu à chaque matérialisation d'entité ; projette-le explicitement si tu en as besoin
Suppression des CAST inutilesvariableUn CAST vers le type déjà stocké empêchait l'usage des index

Nouvelles capacités

// Types complexes et colonnes JSON compatibles avec l'héritage TPT / TPC
mb.Entity<Animal>().UseTptMappingStrategy()
                   .ComplexProperty(a => a.Details, b => b.ToJson());

// Configuration d'une propriété imbriquée par simple chaînage
mb.Entity<MonEntite>().Property(e => e.Details.Description).HasMaxLength(500);

// Clés et index sur des propriétés de types complexes, y compris dans le JSON
mb.Entity<Client>().HasKey(c => c.ClientId.Valeur);
mb.Entity<Client>().HasIndex(c => c.Adresse.CodePostal);
mb.Entity<Commande>().HasIndex("Lignes[].Reference");     // dans une collection JSON

// FullJoin traduit en FULL JOIN
var tout = await db.Clients.FullJoin(db.Commandes,
    c => c.Id, o => o.ClientId, (c, o) => new { Client = c, Commande = o }).ToListAsync(ct);

// MaxBy / MinBy traduits en SQL
var plusActif = await db.Blogs.MaxByAsync(b => b.Billets.Count(), ct);

// Relation EF sans contrainte en base (bases héritées, synchronisation de données)
mb.Entity<Blog>().HasMany(b => b.Billets).WithOne(p => p.Blog)
  .HasForeignKey(p => p.BlogId).ExcludeForeignKeyFromMigrations();

// Colonnes de période d'une table temporelle mappées à de vraies propriétés CLR
mb.Entity<Employe>().ToTable("Employes", t => t.IsTemporal(p =>
{
    p.HasPeriodStart(e => e.DebutPeriode);
    p.HasPeriodEnd(e => e.FinPeriode);
}));
SQL Server : recherche vectorielle indexée, plein texte et recherche hybride
// Index vectoriel géré par les migrations
mb.Entity<Blog>().HasVectorIndex(b => b.Embedding, "cosine");

// Recherche approximative (rapide) au lieu d'un calcul exact sur toutes les lignes
var proches = await db.Blogs
    .VectorSearch(b => b.Embedding, vecteurRequete, "cosine")
    .OrderBy(r => r.Distance)
    .Take(5)
    .WithApproximate()
    .ToListAsync(ct);

// Catalogue et index plein texte créés par migration (fini le SQL manuel)
mb.HasFullTextCatalog("ftCatalog");
mb.Entity<Article>().HasFullTextIndex(a => a.Contenu).UseCatalog("ftCatalog");

// Fonctions table avec score de pertinence
var pertinents = await db.Articles.FreeTextTable("clavier mécanique", a => a.Contenu)
    .OrderByDescending(r => r.Rank)
    .Select(r => r.Value)
    .ToListAsync(ct);

// JSON_CONTAINS sur SQL Server 2025 (au lieu d'OPENJSON) et index JSON
// EF.Functions.JsonContains(...), EF.Functions.JsonPathExists(...)
Outillage des migrations
# Créer ET appliquer en une commande (compilation Roslyn à l'exécution)
dotnet ef database update AjoutProduits --add

# Fichier de configuration : plus besoin de répéter --project et --startup-project
cat .config/dotnet-ef.json
# { "project": "src/App.Infrastructure", "startupProject": "src/App.Api", "context": "AppDbContext" }

# Joker sur les contextes multiples
dotnet ef migrations list --context "*"

# Suppression hors connexion, ou avec une chaîne explicite
dotnet ef migrations remove --offline
dotnet ef database drop --connection "Server=test;..." --force

L'instantané du modèle enregistre désormais l'identifiant de la dernière migration : deux branches divergentes produisent un conflit Git explicite, au lieu d'un historique de migrations cassé découvert en production. Autre changement notable : UseSqlServer passe par défaut au niveau de compatibilité 160 (SQL Server 2022), ce qui active des traductions comme LEAST et GREATEST.

Essayer sans rien casser

# Installer l'aperçu à côté de .NET 10 (les SDK cohabitent sans conflit)
# → https://dotnet.microsoft.com/download/dotnet/11.0

# Épingler la version par projet pour ne pas basculer toute la machine
cat > global.json <<'EOF'
{ "sdk": { "version": "11.0.100-preview.6", "rollForward": "latestPreview" } }
EOF

# Une branche dédiée, un TFM modifié, et on regarde ce qui casse
# <TargetFramework>net11.0</TargetFramework>
dotnet build -warnaserror
dotnet test
Faut-il migrer en .NET 11 à sa sortie ?
SituationRecommandation
Application interne, équipe qui met à jour régulièrementOui : les gains EF Core et les unions valent le détour, et la montée vers .NET 12 sera simple
Produit livré à des clients, mises à jour raresNon : reste en .NET 10 LTS jusqu'à .NET 12 (LTS, fin 2027)
Bibliothèque publiée sur NuGetCible plusieurs TFM (net8.0;net10.0) et n'exige net11.0 que si tu utilises ses nouveautés
Nouveau projet démarré en octobre 2026.NET 10, puis évaluation de .NET 11 après sa sortie

Vérifie que c'est passé

🎯 Quiz — 4 questions

1. Ton produit est livré à des clients qui mettent à jour une fois par an. Migrer en .NET 11 dès sa sortie ?

Un STS arrive en fin de support avant la prochaine LTS : tu devrais migrer deux fois au lieu d'une.

2. Qu'apporte une union C# 15 par rapport à une classe de base et des if (x is …) ?

C'est un filet de sécurité : ajouter un cas d'erreur signale immédiatement tous les endroits à mettre à jour.

3. Que faut-il modifier dans ton code pour bénéficier du runtime async ?

Le bénéfice le plus immédiat est la lisibilité des piles d'appels dans les traces d'erreur asynchrones.

4. En EF Core 11, une propriété SqlVector<float> n'est plus chargée par défaut. Comment la récupérer ?

Elle reste utilisable dans WHERE et ORDER BY ; seule la matérialisation automatique de l'entité l'exclut — d'où les gains spectaculaires mesurés.
À retenir

Sources principales de ce chapitre : What's new in .NET 11 · What's new in C# 15 · What's new in ASP.NET Core 11 · What's New in EF Core 11 · .NET 11 Preview 6 (consultées le 30 juillet 2026).