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.
- Un aperçu n'est pas supporté en production. Il sert à tester, à préparer une migration, à donner du retour aux équipes .NET.
- Les API décrites peuvent être renommées ou retirées avant la version finale — plusieurs l'ont déjà été entre les aperçus 1 et 6.
- .NET 11 est une version STS : dix-huit à vingt-quatre mois de support. Pour un logiciel destiné à durer sans mise à jour fréquente, reste sur .NET 10 (LTS) et attends .NET 12 (fin 2027).
- C# 15 : types
union, hiérarchiesclosed, indexeurs d'extension,break/continueétiquetés, arguments dans les expressions de collection. - Runtime async : la suspension asynchrone devient une fonction du runtime — piles d'appels lisibles,
moins d'allocations, activé par défaut pour
net11.0. - ASP.NET Core : validation asynchrone, protection CSRF automatique, OpenAPI 3.2 par défaut,
attribut
[ShortCircuit], traçage OpenTelemetry natif. - Blazor : rendu statique presque à parité avec MVC (TempData, validation client), défilement
programmé de
Virtualize, pause de circuit, sortie WebAssembly plus légère. - EF Core 11 : SQL nettement meilleur sur les jointures « vers-un » (~29 %), vecteurs non chargés par
défaut (jusqu'à ×9 sur un scénario réel),
FullJoin, recherche plein texte gérée par migrations. - Divers : compression Zstandard, nouveaux types de
Streamsans copie,dotnet testrevu, modèle de serveur MCP livré avec le SDK.
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.
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
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.
| Effet | Détail |
|---|---|
| Piles d'appels lisibles | Fini 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'allocations | Plus de machine à états ni d'objet de continuation dans les cas simples |
| Activé par défaut | Plus besoin de <EnablePreviewFeatures> pour un projet net11.0 ; les bibliothèques du framework sont déjà compilées ainsi |
| Optimisations supplémentaires | Continuations 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,SequenceEqualreplié 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 deJoin/GroupJoinrenvoyant des tuples - Validation asynchrone :
AsyncValidationAttribute,IAsyncValidatableObject,Validator.ValidateObjectAsync - Quatre nouveaux
Streamsans 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,TryGetProcessByIdX25519DiffieHellman,Random.NextInteger<T>,EqualityComparer<T>.Create
SDK et CLI
dotnet testrevu :--no-dependencies, motifs d'exclusion, compteurs par assembly, affichage des tests en cours- Applications à fichier unique :
#:includepour découper, et inclusion directe d'une DLL dotnet run -e CLE=valeurpour passer des variables d'environnementdotnet 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éfaut | Description 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 JSON | Un 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 natif | Les attributs de trace sont émis par le framework : chaque requête est tracée sans instrumentation supplémentaire |
| Filtres d'endpoint et échecs de liaison | Le pipeline de filtres s'exécute même quand la liaison des paramètres échoue : tu peux substituer ta propre réponse 400 |
| SignalR | Rafraîchissement de l'authentification sans couper la connexion ; annulation d'une invocation depuis le client |
| Compression Zstandard | En réponse et en décompression de requête |
| Divers | IOutputCachePolicyProvider, 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 MVC | TempData, 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 asynchrone | EditForm attend les validateurs asynchrones de bout en bout (recherche en base, appel d'API) |
Virtualize : InitialIndex + ScrollToIndexAsync | Ouvrir 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 serveur | Libère les ressources d'un onglet inactif, reprise ensuite : moins de mémoire serveur en Blazor Server |
| Sortie WebAssembly plus légère | Publication réduite ; démarrage plus rapide côté client |
| Navigation relative | NavigationManager.NavigateTo et NavLink acceptent un URI relatif à l'URI courant |
| Composants nouveaux | DisplayName (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 |
QuickGrid | OnRowClick, et diverses améliorations d'ergonomie |
| Localisation des erreurs | Messages de validation et d'erreur traduisibles, en Blazor comme en Minimal API |
| WebAssembly | IHostedService 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
| Optimisation | Gain 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 à distance | Un 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 inutiles | variable | Un 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);
}));
// 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(...)
# 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
| Situation | Recommandation |
|---|---|
| Application interne, équipe qui met à jour régulièrement | Oui : 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 rares | Non : reste en .NET 10 LTS jusqu'à .NET 12 (LTS, fin 2027) |
| Bibliothèque publiée sur NuGet | Cible 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 ?
2. Qu'apporte une union C# 15 par rapport à une classe de base et des if (x is …) ?
3. Que faut-il modifier dans ton code pour bénéficier du runtime async ?
4. En EF Core 11, une propriété SqlVector<float> n'est plus chargée par défaut. Comment
la récupérer ?
WHERE et ORDER BY ; seule la
matérialisation automatique de l'entité l'exclut — d'où les gains spectaculaires mesurés.- 10 novembre 2026, version STS : à évaluer, pas à déployer aveuglément.
- C# 15 :
unionetclosedapportent l'exhaustivité vérifiée — le vrai saut conceptuel. - Runtime async : piles d'appels enfin lisibles, sans changer une ligne.
- ASP.NET Core : validation asynchrone, CSRF automatique, OpenAPI 3.2, traçage natif.
- EF Core 11 : les gains de performance sont les plus tangibles de la version.
- Teste dans une branche avec un
global.jsonépinglé : coût faible, information précieuse.
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).