Nouveautés de C# 15

C# 15 inclut les nouvelles fonctionnalités suivantes. Essayez ces fonctionnalités à l’aide de la dernière version insiders de Visual Studio 2026 ou du Kit de développement logiciel (SDK) .NET 11 preview :

C# 15 est la dernière version en préversion C#. Les versions en préversion de .NET 11 prennent en charge C# 15. Pour plus d’informations, consultez Contrôle de version du langage C#.

Vous pouvez télécharger le dernier SDK .NET 11 en préversion à partir de la page de téléchargements .NET. Vous pouvez également télécharger Visual Studio 2026 Insider, qui inclut le .NET 11 SDK Preview.

La page « Nouveautés en C# » ajoute de nouvelles fonctionnalités lorsqu’elles sont disponibles dans les versions en préversion publique. La section ensemble de travail de la page d’état des fonctionnalités de Roslyn suit le moment où les nouvelles fonctionnalités sont fusionnées dans la branche principale.

Vous pouvez trouver tous les changements cassants introduits dans C# 15 dans notre article sur les changements cassants.

Note

Nous sommes intéressés par vos commentaires sur ces fonctionnalités. Si vous rencontrez des problèmes avec l’une de ces nouvelles fonctionnalités, créez un nouveau problème dans le référentiel dotnet/roslyn.

Arguments d'expression de collection

Vous pouvez passer des arguments au constructeur ou à la méthode de fabrique de la collection sous-jacente à l’aide d’un with(...) élément comme premier élément d’une expression de collection. Cette fonctionnalité vous permet de spécifier la capacité, les comparateurs ou d’autres paramètres de constructeur directement dans la syntaxe d’expression de collection.

L’exemple suivant montre comment passer un argument de capacité à un List<T> constructeur et un comparateur à un HashSet<T>:

string[] values = ["one", "two", "three"];

// Pass capacity argument to List<T> constructor
List<string> names = [with(capacity: values.Length * 2), .. values];

// Pass comparer argument to HashSet<T> constructor
HashSet<string> set = [with(StringComparer.OrdinalIgnoreCase), "Hello", "HELLO", "hello"];
// set contains only one element because all strings are equal with OrdinalIgnoreCase

Pour en savoir plus sur les arguments d’expression de collection, consultez l’article de référence de langage sur les expressions de collection ou la spécification de fonctionnalité. Pour plus d’informations sur l’utilisation d’arguments d’expression de collection dans les initialiseurs de collection, consultez Object et Collection Initializers.

Types d'union

C# 15 introduit des types union, qui représentent une valeur qui peut être l’un des types de cas. Déclarez une union avec le union mot clé :

public record class Cat(string Name);
public record class Dog(string Name);
public record class Bird(string Name);

public union Pet(Cat, Dog, Bird);

Les unions fournissent des conversions implicites à partir de chaque type de cas, et le compilateur garantit que switch les expressions sont exhaustives dans tous les types de cas :

Pet pet = new Dog("Rex");

string name = pet switch
{
    Dog d => d.Name,
    Cat c => c.Name,
    Bird b => b.Name,
};

Le runtime inclut les types UnionAttribute et IUnion commençant par .NET 11 Preview 5. Certaines fonctionnalités de la spécification de la proposition ne sont pas encore implémentées. Ces fonctionnalités seront disponibles dans les prochaines préversions.

Pour plus d’informations, consultez les types d'union dans la référence linguistique ou la spécification des fonctionnalités.

Hiérarchies fermées

À compter de C# 15, vous pouvez appliquer le closed modificateur à une classe pour déclarer une hiérarchie fermée. Une classe close ne peut être dérivée que depuis l’assembly dans lequel elle est déclarée, ce qui fige l’ensemble des classes dérivées directes au moment de la compilation :

public closed record class GateState;
public record class Closed : GateState;
public record class Open(float Percent) : GateState;

Étant donné que le compilateur connaît chaque descendant direct, une switch expression qui gère chacun est exhaustive et n’a pas besoin d’un bras par défaut :

string Describe(GateState state) => state switch
{
    Closed => "closed",
    Open(var percent) => $"{percent}% open",
    // No warning: every direct descendant of 'GateState' is handled.
};

Le closed modificateur est un mot clé contextuel. Une closed classe est implicitement abstract et ne peut pas être combinée avec sealed, staticou un modificateur explicite abstract . La dérivation n’est pas transitive : une classe dérivée non close d’une classe close peut toujours servir de base à une dérivation dans d’autres assemblages. Pour étendre la vérification exhaustive de la hiérarchie, marquez également les descendants intermédiaires closed .

Pour plus d’informations, consultez les modèles de modificateur fermé et de hiérarchie fermée dans la référence du langage ou la spécification de la fonctionnalité.

Indexeurs d’extensions

À compter de C# 15, vous pouvez déclarer des indexeurs dans un extension bloc. Les indexeurs d’extension permettent d’accéder par index à un récepteur comme si l’indexeur était déclaré sur le type du récepteur. Étant donné que les indexeurs sont toujours membres d’instance, un bloc d’extension qui déclare un indexeur doit fournir un paramètre de récepteur nommé.

L’exemple suivant déclare un indexeur get uniquement sur IEnumerable<int> lequel retourne l’élément à une position spécifiée :

public static class SequenceIndexer
{
    extension(IEnumerable<int> sequence)
    {
        public int this[int index] => sequence.ElementAt(index);
    }
}

Vous indexez dans le récepteur comme si l’indexeur était membre du type de récepteur :

IEnumerable<int> numbers = Enumerable.Range(1, 10);
int third = numbers[2];

Pour plus d’informations, consultez la déclaration d’extension dans la référence de langue ou la spécification de la fonctionnalité.

Étiquetés break et continue

À partir de C# 15, les instructions break et continue peuvent nommer une étiquette dans une construction englobante. Utilisez une instruction break avec étiquette pour quitter une boucle englobante ou une instruction switch. Utilisez une étiquette continue pour démarrer l’itération suivante d’une boucle englobante.

Les instructions étiquetées break et continue remplacent les solutions de contournement que vous utiliseriez autrement pour piloter le flux de contrôle dans des boucles imbriquées, comme un indicateur booléen que vous définissez dans une boucle interne puis vérifiez à chaque niveau externe, ou un goto qui permet de sortir des boucles. Le fait de nommer directement la boucle cible dans l’instruction de saut supprime ce suivi et rend le flux de contrôle attendu plus facile à lire.

outer: for (int row = 0; row < grid.Height; row++)
{
    for (int column = 0; column < grid.Width; column++)
    {
        if (grid[row, column].IsBlocked)
        {
            continue outer;
        }

        if (grid[row, column].IsGoal)
        {
            break outer;
        }
    }
}

Placez l’étiquette directement sur la boucle ou l’instruction switch qu’elle identifie. Sans étiquette, break et continue conservent leur comportement d’origine et ciblent l’instruction applicable la plus imbriquée.

La règle de style IDE0410 signale l’indicateur booléen et goto les modèles qu’une instruction de saut étiquetée peut remplacer, et affiche des exemples avant/après pour chacun.

Pour plus d’informations, consultez instructions Jump dans la référence de langage ou la spécification de fonctionnalité.

Sécurité de la mémoire

C# 15 lance une initiative sur plusieurs versions pour redéfinir la sécurité de la mémoire au sein du langage. L’objectif est de lier le unsafe contexte aux opérations qui accèdent réellement à la mémoire non managée, plutôt qu’à l’existence de types de pointeurs. La plupart des vulnérabilités de sécurité de la mémoire proviennent de ces opérations d’accès, de sorte que le langage les distingue pour les réviseurs et les auditeurs.

Dans le modèle complet, unsafe appliqué à un membre indique que celui-ci est requires-unsafe : l’obligation d’audit incombe à l’appelant, qui doit utiliser ce membre dans un contexte unsafe. Un assembly choisit d’activer cette contrainte, et le compilateur enregistre ce choix à l’aide de l’attribut System.Runtime.CompilerServices.MemorySafetyRulesAttribute. Le modèle ajoute également un safe mot clé contextuel qui marque les extern membres et les champs de disposition explicite comme sécurisés. Ensemble, ces règles rendent explicites les limites des risques potentiels d’insécurité mémoire dans l’ensemble d’un programme.

La première étape comprend les relaxations de pointeur. Lorsque vous compilez avec la version de preview langue, les opérations suivantes ne nécessitent plus de unsafe contexte :

  • Déclaration d’un type de pointeur et prise de l’adresse d’une variable avec l’opérateur & .
  • L’instruction fixed, qui fixe une variable.
  • Conversion d’une stackalloc expression en pointeur.
  • Opérateur sizeof appliqué à n’importe quel type non managé.

L’exemple suivant crée et fixe un pointeur sans contexte unsafe :

int number = 42;
int* pointer = &number;

int[] numbers = [10, 20, 30];
fixed (int* first = numbers)
{
    // Dereferencing the pointer still requires an unsafe context.
}

Les opérations qui accèdent à la mémoire pointée, telles que l’indirection de pointeur (*p), l’accès aux membres du pointeur (p->member), l’accès à un élément pointé (p[i]) et l’appel via un pointeur de fonction, nécessitent toujours un contexte unsafe.

C# 15 ajoute également une unsafe expression, unsafe(expression)qui établit un contexte non sécurisé pour une seule expression. Il est utile là où un bloc unsafe ne peut pas apparaître dans la syntaxe, par exemple dans un initialiseur de champ, un initialiseur de constructeur ou un filtre catch :

class Header
{
    // A field initializer can't contain an unsafe block, but it can contain an unsafe expression.
    static readonly int Signature = unsafe(ReadSignature());

    static unsafe int ReadSignature()
    {
        int rawValue = 0x1234;
        int* pointer = &rawValue;
        return *pointer;
    }
}

Comme le reste de la préversion de la sécurité de la mémoire, les expressions unsafe nécessitent la version preview du langage et l’option de compilateur AllowUnsafeBlocks.

Le compilateur reconnaît également le mot-clé contextuel safe comme modificateur des membres extern et des champs à disposition explicite. Toutefois, le modèle de membre requires-unsafe et l’activation au niveau de l’assembly pour les règles de sécurité de la mémoire mises à jour ne sont pas encore disponibles, et safe et unsafe n’ont actuellement aucun effet sur le code appelant.

Pour plus d’informations, consultez Code non sécurisé, types de pointeurs et pointeurs de fonction dans la référence du langage ou la spécification de fonctionnalité.

Voir aussi