Modifications majeures de Roslyn de .NET 10.0.100 à .NET 11.0.100

Ce document répertorie les changements cassants connus dans Roslyn après la version générale de .NET 10 (kit SDK .NET version 10.0.100) jusqu'à la version générale de .NET 11 (kit SDK .NET version 11.0.100).

Le contexte sécurisé d’une expression de collection de type Span/ReadOnlySpan est désormais un bloc de déclaration

Introduit dans Visual Studio 2026 version 18.3

Le compilateur C# a apporté un changement cassant afin de respecter correctement les règles de sécurité ref dans la spécification de la fonctionnalité expressions de collection. Plus précisément, la clause suivante :

  • Si le type cible est un type d'étendueSystem.Span<T> ou System.ReadOnlySpan<T>, le safe-context de l'expression de collection est le declaration-block.

Auparavant, le compilateur utilisait le membre de fonction de contexte sécurisé dans cette situation. Nous avons maintenant apporté une modification à l’utilisation du bloc de déclaration conformément à la spécification. Cela peut entraîner l’apparition de nouvelles erreurs dans le code existant, comme dans le scénario ci-dessous :

scoped Span<int> items1 = default;
scoped Span<int> items2 = default;
foreach (var x in new[] { 1, 2 })
{
    Span<int> items = [x];
    if (x == 1)
        items1 = items; // previously allowed, now an error

    if (x == 2)
        items2 = items; // previously allowed, now an error
}

Si votre code est affecté par ce changement cassant, envisagez plutôt d'utiliser un type tableau pour les expressions de collection concernées :

scoped Span<int> items1 = default;
scoped Span<int> items2 = default;
foreach (var x in new[] { 1, 2 })
{
    int[] items = [x];
    if (x == 1)
        items1 = items; // ok, using 'int[]' conversion to 'Span<int>'

    if (x == 2)
        items2 = items; // ok
}

Vous pouvez également déplacer l'expression de collecte vers une étendue où l'affectation est autorisée :

scoped Span<int> items1 = default;
scoped Span<int> items2 = default;
Span<int> items = [0];
foreach (var x in new[] { 1, 2 })
{
    items[0] = x;
    if (x == 1)
        items1 = items; // ok

    if (x == 2)
        items2 = items; // ok
}

Voir aussi https://github.com/dotnet/csharplang/issues/9750.

Les scénarios exigeant que le compilateur synthétise un délégué retournant ref readonly nécessitent désormais la disponibilité du type System.Runtime.InteropServices.InAttribute.

Introduit dans Visual Studio 2026 version 18.3

Le compilateur C# a apporté un changement cassant afin d'émettre correctement les métadonnées lorsque ref readonly est retourné par les délégués synthétisés

Cela peut entraîner une « erreur CS0518 : Le type prédéfini « System.Runtime.InteropServices.InAttribute » n’est pas défini ou importé » pour apparaître dans le code existant, comme dans les scénarios ci-dessous :

var d = this.MethodWithRefReadonlyReturn;
var d = ref readonly int () => ref x;

Si votre code est affecté par cette modification incompatible, envisagez d'ajouter une référence à un assembly définissant System.Runtime.InteropServices.InAttribute dans votre projet.

Les scénarios utilisant des ref readonly fonctions locales nécessitent désormais la disponibilité du System.Runtime.InteropServices.InAttribute type.

Introduit dans Visual Studio 2026 version 18.3

Le compilateur C# a apporté un changement cassant afin d'émettre correctement les métadonnées pour les fonctions locales retournant ref readonly.

Cela peut entraîner une « erreur CS0518 : Le type prédéfini « System.Runtime.InteropServices.InAttribute » n’est pas défini ou importé » pour apparaître dans le code existant, comme dans le scénario ci-dessous :

void Method()
{
    ...
    ref readonly int local() => ref x;
    ...
}

Si votre code est affecté par cette modification incompatible, envisagez d'ajouter une référence à un assembly définissant System.Runtime.InteropServices.InAttribute dans votre projet.

L'évaluation dynamique des opérateurs &&/|| n'est pas autorisée lorsque l'opérande gauche est statiquement typé comme une interface.

Introduit dans Visual Studio 2026 version 18.3

Le compilateur C# signale désormais une erreur lorsqu'un type d'interface est utilisé comme opérande gauche d'un opérateur logique && ou ||, avec un opérande droit dynamic. Auparavant, le code compilait pour un type d'interface avec des opérateurs true/false, mais échouait au moment de l'exécution avec un RuntimeBinderException, car le liant d'exécution ne peut pas appeler les opérateurs définis sur les interfaces.

Cette modification empêche une erreur d’exécution en la signalant au moment de la compilation à la place. Le message d’erreur est :

erreur CS7083 : L’expression doit être implicitement convertible en booléen ou son type « I1 » ne doit pas être une interface et doit définir l’opérateur « false ».

interface I1
{
    static bool operator true(I1 x) => false;
    static bool operator false(I1 x) => false;
}

class C1 : I1
{
    public static C1 operator &(C1 x, C1 y) => x;
    public static bool operator true(C1 x) => false;
    public static bool operator false(C1 x) => false;
}

void M()
{
    I1 x = new C1();
    dynamic y = new C1();
    _ = x && y; // error CS7083: Expression must be implicitly convertible to Boolean or its type 'I1' must not be an interface and must define operator 'false'.
}

Si votre code est affecté par ce changement provoquant des ruptures, envisagez de modifier le type statique de l’opérande gauche d’un type d’interface à un type de classe concret ou à un type dynamic :

void M()
{
    I1 x = new C1();
    dynamic y = new C1();
    _ = (C1)x && y; // Valid - uses operators defined on C1
    _ = (dynamic)x && y; // Valid - uses operators defined on C1
}

Voir aussi https://github.com/dotnet/roslyn/issues/80954.

nameof(this.) est interdit dans les attributs

Introduit dans Visual Studio 2026 version 18.3 et .NET 10.0.200

L’utilisation du mot clé this ou base à l’intérieur de nameof d’un attribut a été précédemment autorisée de manière non intentionnelle dans Roslyn depuis C# 12 et est désormais correctement interdite pour correspondre à la spécification du langage. Ce changement cassant peut être atténué en supprimant this. et en accédant au membre sans qualificateur.

Voir aussi https://github.com/dotnet/roslyn/issues/82251.

class C
{
    string P;
    [System.Obsolete(nameof(this.P))] // now disallowed
    [System.Obsolete(nameof(P))] // workaround
    void M() { }
}

Analyse syntaxique de « with » dans un bras d'expression switch

Introduit dans Visual Studio 2026 version 18.4

Voir https://github.com/dotnet/roslyn/issues/81837 et https://github.com/dotnet/roslyn/pull/81863

Auparavant, face à l'élément suivant, le compilateur traitait (X.Y)when comme une expression de cast. Plus précisément, la conversion de l’identificateur contextuel when en (X.Y):

x switch
{
    (X.Y) when
}

Cela n'était pas souhaitable et signifiait qu'une simple vérification du modèle (comme when) ne pourrait pas être analysée correctement. Maintenant, cela est traité comme un modèle (X.Y) constant suivi d’un when clause.

with() en tant qu'élément d'expression de collection est traité comme des arguments de construction de collection.

Introduit dans Visual Studio 2026 version 18.4

with(...) lorsqu'il est utilisé comme élément dans une expression de collection, et lorsque LangVersion est défini sur 15 ou sur une version ultérieure, est lié comme arguments transmis au constructeur ou à la méthode de fabrique utilisée pour créer la collection, plutôt que comme expression d'invocation d'une méthode nommée with.

Pour établir une liaison à une méthode nommée with, utilisez @with à la place.

object x, y, z = ...;
object[] items;

items = [with(x, y), z];  // C# 14: call to with() method; C# 15: error args not supported for object[]
items = [@with(x, y), z]; // call to with() method
object with(object a, object b) { ... }

Les types de pointeur ne nécessitent plus de contexte non sécurisé

Introduit dans Visual Studio 2026 version 18.7

Dans une version C# future (actuellement en langversion:preview), les types de pointeurs (par exemple, int*, delegate*<void>) ne nécessitent plus de contexte non sécurisé. Seules les opérations d'indirection de pointeur (déréférencement, accès membre via ->, accès à un élément, etc.) nécessitent unsafe. Cela fait partie de la fonctionnalité d’évolution non sécurisée .

Étant donné que les types de pointeurs sont désormais légaux dans des contextes sûrs, la résolution de surcharge peut considérer les candidats qui ont été précédemment exclus. Cela peut entraîner de nouvelles erreurs d’ambiguïté :

using System;

class Program
{
    static void Main()
    {
        M(x => { }); // C# 14: prints "2"; C# preview: error CS0121 (ambiguous)
    }

    static void M(F1 f) { Console.WriteLine(1); }
    static void M(F2 f) { Console.WriteLine(2); }
}

unsafe delegate void F1(int* x);
delegate void F2(int x);

Auparavant, l’expression lambda x => { } ne pouvait pas se convertir en F1 dans un contexte sûr, car int* nécessitait un contexte non sûr, donc seul M(F2) était applicable. Maintenant que int* est autorisé dans les contextes sécurisés, la lambda est convertible vers les deux délégués, ce qui produit une erreur d'ambiguïté.

Si votre code est affecté par la modification de l’ambiguïté, ajoutez des types de paramètres explicites à l’lambda pour lever l’ambiguïté :

M((int x) => { }); // Resolves to M(F2)

Élément non initialisé stackalloc à l'intérieur de SkipLocalsInit nécessite un contexte non sécurisé

Introduit dans Visual Studio 2026 version 18.7

Dans une future version de C# (actuellement en langversion:preview), une expression stackalloc sans initialiseur à l'intérieur d'une méthode marquée avec [SkipLocalsInit] requiert désormais un contexte dangereux, même si le type cible est Span<T>. Cela est dû au fait qu’il SkipLocalsInit empêche l’initialisation zéro de la mémoire allouée, ce qui permet de lire des données non initialisées , un problème de sécurité de la mémoire. Cela fait partie de la fonctionnalité d’évolution non sécurisée .

[System.Runtime.CompilerServices.SkipLocalsInit]
void M()
{
    Span<int> a = stackalloc int[5];           // previously ok, now error CS9361
    Span<int> b = stackalloc int[] { 1, 2 };   // ok (has initializer)
    Span<int> c = stackalloc int[2] { 1, 2 };  // ok (has initializer)
}

Si votre code est affecté, vous pouvez :

  • Ajoutez un bloc unsafe autour du stackalloc.
    [SkipLocalsInit]
    void M()
    {
        Span<int> a;
        unsafe { a = stackalloc int[5]; }
    }
    
  • Ou fournissez un initialiseur afin que la mémoire soit entièrement initialisée :
    [SkipLocalsInit]
    void M()
    {
        Span<int> a = stackalloc int[5] { 0, 0, 0, 0, 0 };
    }