Nouveautés de C# 15

C# 15 inclut les nouvelles fonctionnalités suivantes. Essayez ces fonctionnalités à l’aide du SDK .NET 11 ou d’une version compatible de Visual Studio 2026 :

C# 15 est la version de langage par défaut pour les projets qui ciblent net11.0 dans le SDK .NET 11. À l’exception de la sécurité de la mémoire, ces fonctionnalités ne nécessitent <LangVersion>preview</LangVersion>pas . Le sdk .NET 11 est toujours en préversion. Pour plus d’informations, consultez Contrôle de version du langage C#.

Vous pouvez également essayer la fonctionnalité de préversion de sécurité de la mémoire. Activez la version préliminaire du langage pour les relaxations de syntaxe et de pointeur, ainsi que l’indicateur de fonctionnalité updated-memory-safety-rules pour les règles de sécurité mises à jour.

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

Utilisez un with(...) élément comme premier élément d’une expression de collection pour passer des arguments au constructeur ou à la méthode de fabrique de la collection sous-jacente. 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,
};

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é.

Membres statiques d'interface non virtuels sur les environnements d'exécution plus anciens

À compter de C# 15, vous pouvez déclarer et utiliser des membres d’interface statique non virtuelle, même lorsque le runtime cible ne prend pas en charge les implémentations d’interface par défaut. Par exemple, une interface peut fournir une méthode d'assistance statique sans exiger que chaque type qui l'implémente fournisse une implémentation :

public interface IMeasurement
{
    public static double ToMeters(double centimeters) => centimeters / 100;
}

Appelez directement cette méthode d'assistance sur l'interface, par exemple IMeasurement.ToMeters(250). Les implémentations par défaut d’instance, les membres static abstract et static virtual, ainsi que les membres d’interface protégés, nécessitent toujours une prise en charge du runtime.

Pour plus d’informations, consultez les membres de l’interface statique et la prise en charge du runtime.

Sécurité de la mémoire

Le SDK .NET 11 inclut une fonctionnalité d’aperçu de la sécurité de la mémoire qui marque le début d’un effort sur plusieurs versions pour redéfinir la sécurité de la mémoire dans le 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.

Lorsque vous activez les règles mises à jour, l’élément unsafe appliqué à un membre le marque comme requires-unsafe : l’obligation d’audit incombe alors à l’appelant, qui doit utiliser ce membre dans un contexte unsafe. Le compilateur enregistre l’utilisation de l’assembly des règles mises à jour avec l’attribut System.Runtime.CompilerServices.MemorySafetyRulesAttribute . Le modèle ajoute également un safe mot clé contextuel qui marque les extern membres et les champs dans des types de disposition explicites ou étendus 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 safe mot clé contextuel sur les déclarations qui peuvent porter un contrat de sécurité, y compris extern les membres et les champs dans des types de disposition explicites ou étendus. Définissez LangVersion sur preview pour activer la nouvelle syntaxe et les assouplissements des pointeurs. Pour activer également les règles mises à jour, y compris les obligations de l’appelant requires-unsafe, activez la fonctionnalité de compilateur updated-memory-safety-rules. Pour connaître la syntaxe du programme basé sur des fichiers et du projet, consultez Activer les règles de sécurité de la mémoire mises à jour.

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