Les fonctionnalités C# 12

Etienne Girardet

Le but de cet article est de résumer et d’expliquer les fonctionnalités de C# 12.0. Dans un premier temps, on explicitera le contexte de C# 12.0 par rapport aux autres composants (frameworks, IDE, compilateur etc…) qui permettent de l’utiliser. Ensuite, on rentrera dans le détail des fonctionnalités.

Précisions sur les versions de C#

Depuis C# 8.0, les évolutions fonctionnelles de C# se font pour .NET seulement (anciennement appelé .NET Core). Le framework .NET est toujours supporté toutefois les nouvelles fonctionnalités ne sont pas implémentées pour cet environnement.
Comme les environnements du framework .NET et de .NET ne subsistent plus en parallèle, l’approche .NET Standard n’a plus d’intérêt. .NET Standard s’arrête donc à la version 2.1. Les versions 5.0, 6.0, 7.0, 8.0 et 9.0 de .NET implémentent .NET Standard de la version 1.0 à 2.1 toutefois il est conseillé de cibler une version de .NET plutôt que .NET Standard.

Chronologie des releases

Ce tableau permet de résumer les dates de sorties de C# 12.0, de Visual Studio, du compilateur Roslyn et des versions .NET.

Date Version C# Version Visual Studio Version .NET Compilateur
Novembre 2020 C# 9.0 VS2019 (16.8) .NET 5.0
(NET Standard 1.0⇒2.1)(1)
Roslyn 3.8.0
Février 2021 VS2019 (16.9) Roslyn 3.9.0
Mai 2021 VS2019 (16.10) Roslyn 3.10.0
Août 2021 VS2019 (16.11)
Novembre 2021 C# 10.0 VS2022 (17.0) .NET 6.0
(NET Standard 1.0⇒2.1)(1)
Roslyn 4.0.1
Décembre 2021 Roslyn 4.1.0
Février 2022 VS2022 (17.1)
Avril 2022 Roslyn 4.2.0
Mai 2022 VS2022 (17.2)
Août 2022 VS2022 (17.3) Roslyn 4.3.1
Novembre 2022 C# 11.0 VS2022 (17.4) .NET 7.0
(NET Standard 1.0⇒2.1)(1)
Roslyn 4.4.0
Février 2023 VS2022 (17.5) Roslyn 4.5.0
Mai 2023 VS2022 (17.6) Roslyn 4.6.0
Août 2023 VS2022 (17.7) Roslyn 4.7.0
Novembre 2023 C# 12.0 VS2022 (17.8) .NET 8.0
(NET Standard 1.0⇒2.1)(1)
Roslyn 4.8.0
Février 2024 VS2022 (17.9) Roslyn 4.9.2
Mai 2024 VS2022 (17.10) Roslyn 4.10.0
Août 2024 VS2022 (17.11) Roslyn 4.11.0
Novembre 2024 C# 13.0 VS2022 (17.12) .NET 9.0 Roslyn 4.12.0
Février 2025 VS2022 (17.13) Roslyn 4.13.0
Mai 2025 VS2022 (17.14) Roslyn 4.14.0
Novembre 2025 ? C# 14.0 VS2022 (17.?) .NET 10.0 Roslyn 4.?.?

(1): .NET Standard n’est plus nécessaire puisque les 2 environnements framework .NET et .NET Core n’évoluent plus fonctionnellement. Ils ont laissé place à l’environnement uniformisé .NET (voir .NET 5+ and .NET Standard pour plus de détails).

Lien entre la version C# et le compilateur

Le tableau précédent permet d’indiquer la version de C# dans le contexte des frameworks de façon à avoir une idée des sorties des autres éléments de l’environnement .NET. Toutefois, la version de C# est liée à la version du compilateur C#. Le compilateur est ensuite livré avec Visual Studio (depuis Visual Studio 2017 15.3) ou avec le SDK .NET.

Le chemin du compilateur est lié au composant avec lequel il est livré:

  • Avec Visual Studio: par exemple pour Visual Studio 2022 Professional: C:\Program Files (x86)\Microsoft Visual Studio\2022\Professional\MSBuild\Current\Bin\Roslyn\csc.exe
  • Avec les Build tools: par exemple pour les Build Tools for Visual Studio 2022: C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\MSBuild\Current\Bin\Roslyn\csc.exe
  • Avec le SDK .NET:
    • Sur Linux: /usr/share/dotnet/sdk/<version>/Roslyn/bincore/csc.dll
    • Sur Windows: C:\Program Files\dotnet\sdk\<version>\Roslyn\bincore\csc.dll

On peut connaître la version du compilateur en tapant:

csc -help

On peut savoir quelles sont les versions de C# que le compilateur peut gérer en exécutant:

csc -langversion:? 

Limiter la version C# à compiler

Par défaut, le compilateur compile dans les versions suivantes de C#:

  • .NET 9.0: C# 13.0
  • .NET 8.0: C# 12.0
  • .NET 7.0: C# 11.0
  • .NET 6.0: C# 10.0
  • .NET 5.0: C# 9.0
  • Framework .NET: C# 7.3
  • .NET Core 3.x: C# 8.0
  • .NET Core 2.x: C# 7.3
  • .NET Standard 2.1: C# 8.0
  • .NET Standard 2.0: C# 7.3
  • .NET Standard 1.x: C# 7.3

On peut volontairement limiter la version C# que le compilateur va traiter:

  • Dans Visual Studio: dans les propriétés du projet ⇒ Onglet Build ⇒ Advanced ⇒ Paramètre Language version.
  • En éditant directement le fichier .csproj du projet et en indiquant la version avec le paramètre LangVersion:
    <Project Sdk="Microsoft.NET.Sdk"> 
      <PropertyGroup> 
        <OutputType>Exe</OutputType> 
        <TargetFramework>net8.0</TargetFramework> 
        <LangVersion>12.0</LangVersion>
      </PropertyGroup> 
    </Project> 
    

Fonctionnalités C# 12

Les fonctionnalités les plus basiques de C# 12.0 sont présentées dans cet article. Les inline arrays sont présentés dans un article séparé.

Primary constructors

Cette fonctionnalité permet en une seule ligne de:

  • Créer un constructeur implicite,
  • D’ajouter des données membres à une classe, une struct ou un record,
  • D’initialiser ces données membres.

Ainsi, les 2 extraits de code suivants sont équivalents:

public class Rectangle(int length, int width);

Est équivalent à:

public class Rectangle
{
  private int length;
  private int width;
  
  public Rectangle(int length, int width)
  {
    this.length = length;
    this.width = width;
  }
}

Lorsqu’un constructeur primaire est déclaré, cela entraîne quelques implications suivant le type d’objet:

  • Pour une classe: il n’y a plus de constructeur implicite sans paramètres.
    Ainsi:

    var rectangle = new Rectangle();  // ATTENTION: génère une erreur
    
  • Pour une struct: un constructeur sans paramètre implicite est rajouté à la compilation. Ce constructeur sans paramètre initialise toutes les données membres.
    Par exemple, si on déclare une struct de cette façon:

    public struct Circle(int radius);
    

    Un constructeur sans paramètre implicite est créé:

    var circle = new Circle();  // OK
    
  • Pour un record: un accesseur en lecture est rajouté implicitement pour chaque donnée membre.
    Ainsi:

    public record Car(string Brand, string Model);
    

    Est équivalent à:

    public record Car
    {
      public Car(string brand, string model)
      {
        this.Brand = brand;
        this.Model = model;
      }
    
      public string Brand { get; }
      public string Model { get; }
    }
    

Pour plus de détails sur les records: cdiese.fr/csharp9_records/.

Collection expressions

L’objectif de cette fonctionnalité est de faciliter l’instanciation de collections en permettant 2 nouvelles écritures syntaxiques:

  • L’instanciation d’une collection directement avec les éléments entre crochets.
  • Opérateur spread.

Instancier une collection directement avec les éléments entre crochets

Par exemple, on peut désormais instancier un liste de cette façon:

List<int> items1 = [1, 2, 3, 4];

Cette syntaxe a plusieurs conséquences:

  • La syntaxe [ ] permet de créer des collections de type différent. Ainsi le type de la collection créée ne peut être déterminé que par ce qui est indiqué devant la variable (dans l’exemple List<int>). Cela signifie qu’il n’est pas possible d’utiliser l’opérateur var:
    var items1 = [1, 2, 3, 4]; // NE COMPILE PAS
    
  • Il est possible de créer d’autres types de collection en utilisant la même syntaxe:
    List<int> collection1 = [1, 2, 3, 4];
    int[] collection2 = [1, 2, 3, 4];
    Span<int> collection3 = [1, 2, 3, 4];
    IEnumerable<int> collection4 = [1, 2, 3, 4];
    ICollection<int> collection5 = [1, 2, 3, 4];
    

On peut aussi créer des tableaux en 2 dimensions en indiquant des tableaux entre crochets:

int[] array1 = [1, 2, 3, 4];
int[] array2 = [5, 6, 7, 8];
int[] array3 = [8, 9, 10, 11];
int[][] twoDimArray = [array1, array2, array3];

Le contenu de twoDimArray est:

[[1, 2,  3,  4]
 [5, 6,  7,  8]
 [8, 9, 10, 11]]

Cette syntaxe n’est possible que pour les tableaux, elle ne permet pas de créer d’autres types de collection.
Pour créer une autre collection à 1 dimension à partir d’une collection existante avec la syntaxe [ ], il faut utiliser l’opérateur spread introduit aussi avec C# 12.

Opérateur spread

Cet opérateur permet d’instancier une collection à partir d’un énumérable en utilisant la syntaxe:

[.. <Enumerable>]

Comme précédemment, la syntaxe de la nouvelle collection est déterminée par le type indiqué avant le nom de la variable:
Si on considère:

int[] array1 = [1, 2, 3, 4];

On peut écrire:

List<int> collection1 = [..array1];
int[] collection2 = [..array1];
Span<int> collection3 = [..array1];
IEnumerable<int> collection4 = [..array1];
ICollection<int> collection5 = [..array1];

Si on utilise directement la syntaxe [ ] sans indiquer explicitement le type, il y aura une erreur à la compilation:

foreach (var element in [..array1])   // NE COMPILE PAS
{   }

Il faut indiquer le type explicitement:

foreach (var element in (List<int>)[..array1])
{   }

L’opérateur spread énumère l’enumerable qui se trouve derrière les .., on peut donc écrire:

int[] array1 = [1, 2, 3, 4];
int[] array2 = [5, 6, 7, 8];
List<int> collection1 = [..array1, ..array2];

Le contenu de collection1 sera:

1, 2, 3, 4, 5, 6, 7, 8
Ne pas confondre l’opérateur spread (C# 12) et l’opérateur range (C# 11)

Avant C# 12, il existait déjà une notation .. qui correspond à l’opérateur range (introduit en C# 8) qui permet d’énumérer le contenu d’une collection avec une syntaxe:

<Index> .. <Index>

L’opérateur range permet d’instancier un objet de type System.Range qui servira à effectuer une énumération, par exemple:

List<int> example = new List<int>{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
var sample1 = example[2..5];    // Intervalle de 2 à 5 exclus: 2, 3, 4
var sample2 = example[2..];     // Intervalle de 0 à la fin: 2, 3, 4, 5, 6, 7, 8, 9
var sample3 = example[..5];     // Intervalle de 0 à 5 exclus: 0, 1, 2, 3, 4
var sample4 = example[..^3];    // Intervalle de 0 jusqu'à 3 index avant la fin: 0, 1, 2, 3, 4, 5, 6
var sample5 = example[..];      // Intervalle de 0 à la fin: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9

Il existe une autre notation utilisant aussi .. qui correspond au range pattern dans le cadre du pattern matching (à partir de C# 11). Voir List pattern dans cdiese.fr/cheat-sheet-pattern-matching/.

ref readonly en argument de méthode

Cette fonctionnalité est plus avancée que les précédentes et nécessite la compréhension de plusieurs notions pour être appréhendée. Elle consiste à permettre d’utiliser ref readonly pour les paramètres d’une méthode. Le modificateur ref readonly existait déjà quand on déclarait une variable depuis C# 7.2 mais il n’était pas possible de l’utiliser en paramètre de méthode (on ne pouvait utiliser que ref). Depuis C# 7.2, il existait l’opérateur in en paramètre de méthode qui assurait à peu près la même fonctionnalité.

Pour comprendre cette fonctionnalité, il est nécessaire de rappeler quelques caractéristiques de C#:

  • En C#, on considère les objets de type référence (classe et record) et les objets de type valeur (struct, record struct, enum, type primitif comme int, floatw, double, byte etc…).
  • Les objets de type référence sont manipulés par référence lors des copies de variable ou lors des passages en argument de méthode. Une référence est un objet de type valeur. Ainsi la référence est copiée mais l’objet référencé n’est pas copié.
  • Les objets de type valeur sont manipulés en effectuant des copies lors des copies de variable ou lors des passages en argument de méthode. L’objet est copié entièrement lors de ces manipulations.

Avant C# 12

Le mot-clé ref permet d’effectuer des manipulations d’objets de type valeur par référence:

  • Pour un objet de type valeur, on manipule une référence vers cet objet (plus précisément on utilise un objet ref qui pointe vers l’objet de type valeur).
  • Pour un objet de type référence, on manipule une référence de la référence vers l’objet (on utilise un objet ref qui pointe vers la référence de l’objet de type référence, la référence d’un objet de type référence étant elle-même un objet de type valeur).

Ainsi:

Syntaxe Remarques
Passage en argument
par référence
(ref)
void MethodName(ref <type> argument)
{ ... }
Par exemple:

private static void ChangeRadius(int newRadius, 
    ref Circle circle)
{
  circle.Radius = newRadius;
}
Manipulation
d’une variable locale
(ref)
ref var refVariable = 
  ref <value variable>;
Par exemple:

Circle circle = new Circle();
ref Circle circleRef = ref circle;

Réaffectation d’une référence:

// NE PAS OUBLIER ref
<variable par référence 2> = 
  ref <variable par référence 1> 

Par exemple:

Circle firstCircle = new Circle ...
// ...
// Affectation d’une référence
secondCircleRef = ref firstCircleRef; 
// Affectation par valeur, une copie est effectuée
secondCircleRef = firstCircleRef; 
Manipulation
d’une variable locale
en lecture seule
(ref readonly)
ref readonly var refVariable = 
  ref <value variable>;
Toutes les variables ref peuvent être affectées en readonly.
L’affectation d’un membre sur une variable ref en lecture seule n’est pas possible.

Par exemple:

Circle circle = new Circle { Radius = 2; }
// Référence en lecture seule à partir 
// d’un objet de type valeur
ref readonly readOnlyCircleRef = ref circle; 
readOnlyCircleRef.Radius = 4; // ERREUR

// Référence en lecture/écriture
ref var circleRef = ref circle; 
// Référence en lecture seule à partir 
// d’une autre référence
ref readonly otherReadOnlyCircleRef = ref circleRef; 

ATTENTION: Pour éviter les defensive copies, il est préférable que la structure soit immutable (cf. readonly struct).

Retour de fonction
par référence
(return ref)
ref <type> FunctionName()
{
  // ...
  return ref <variable>;
}
La variable retournée par référence doit être accessible à l’extérieur de la stack frame correspondant à la fonction, il peut s’agir:

  • D’un membre d’un objet de type référence:
    class CircleWrapper
    {
      private Circle InnerCircle = 
        new Circle { Radius = 2 };
    
      public ref Circle GetCircle()
      {
        return ref this.InnerCircle; // OK l’objet 
        // est membre d’un objet de type référence
      }
    }
  • D’un objet de type valeur statique:
    static Circle staticCircle = new Circle { Radius = 2};
    ref Circle GetCircle()
    {
      return ref staticCircle; // OK l’objet retourné 
      // est statique
    }
  • D’un objet stocké dans un tableau:
    ref Circle FindCircle(Circle[] circles, 
      int circleIndex)
    {
      return ref circles[circleIndex]; // OK l’objet 
      // appartient à un tableau
    }

Par contre, on ne peut pas retourner une variable locale d’une fonction:

ref Circle GetCircle()
{
  Circle localCircle = new Circle { Radius = 4 };
  return ref localCircle; // ERREUR, l’objet est perdu 
  // à la sortie de la stack frame
}
Retour de fonction
par référence
en lecture seule
(return ref)
ref readonly <type> FunctionName()
{
  // ...
  return ref <variable>;
}
Même restriction que pour un retour de fonction par référence simple.
Un membre en readonly doit obligatoirement être retourné en readonly:

class CircleWrapper
{
  private readonly Circle InnerCircle = 
    new Circle { Radius = 2 };

  // OK retour en readonly
  public readonly ref Circle GetReadOnlyCircle() 
  {
    return ref this.InnerCircle;
  }

  // ERREUR le retour doit être en readonly
  public ref Circle GetCircle()
  {
    return ref this.InnerCircle;
  }
}

L’affectation dans une variable d’une fonction en readonly doit obligatoirement être en readonly.

private static Circle staticCircle = 
  new Circle { Radius = 2};
public static ref readonly Circle GetCircle()
{
  return ref staticCircle;
}

// ...
ref readonly var readOnlyCircle = ref GetCircle(); // OK
ref var readOnlyCircle = ref GetCircle(); // ERREUR

ATTENTION: Pour éviter les defensive copies, il est préférable que la structure soit immutable (cf. readonly struct).

Indiquer qu’un argument
de méthode est en
lecture seule avec in
void MethodName(in <type> argument) 
{ ... }
L’affectation d’un membre d’un argument avec in n’est pas possible.
Par exemple:

struct Circle
{
  public int Radius;

  public void SetRadiusFromInside(int newRadius)
  {
    this.Radius = newRadius;
  }
}

static void ChangeRadiusFromOutside(in Circle circle, 
  int newRadius)
{
  // ERREUR à cause de in
  circle.Radius = newRadius; 

  // Pas d’erreur mais la valeur n’est pas modifiée 
  // à cause de la defensive copy
  circle.SetRadiusFromInside(newRadius); 
  // ATTENTION: il faut éviter de modifier 
  // la valeur de l’argument dans le corps de la méthode
}

ATTENTION: Pour éviter les defensive copies, il est préférable que la structure soit immutable (cf. readonly struct).

Pour plus de détails, voir Type valeur vs type référence.

A partir de C# 12

Il est possible d’utiliser ref readonly en argument de méthode pour éviter les modifications d’un objet à l’intérieur de la méthode.

Par exemple si on considère:

struct Circle
{
  public int Radius;

  public void SetRadiusFromInside(int newRadius)
  {
    this.Radius = newRadius;
  }
}

static void ChangeRadiusFromOutside(ref Circle circle, int newRadius)
{
  circle.Radius = newRadius; 
}

// circle peut être modifié dans la méthode ChangeRadiusFromOutside():
var circle = new Circle{ Radius = 4 }; 
Console.WriteLine(circle.Radius); // 4 
ChangeRadiusFromOutside(ref circle, 2); // circle est passé par référence 
Console.WriteLine(circle.Radius); // 2

Pour éviter de modifier circle dans ChangeRadiusFromOutside(), on peut utiliser ref readonly:

static void ChangeRadiusFromOutside(ref readonly Circle circle, int newRadius) 
{ 
  // circle.Radius = newRadius; // ERREUR car on ne peut pas modifier circle dans le corps de la méthode 
  circle.SetNewRadius(newRadius); // Pas d'erreur mais cette ligne n'aura pas d'effets
  Console.WriteLine(circle.Radius);  
}

Si on effectue l’appel suivant:

var circle = new Circle{ Radius = 4 }; 
Console.WriteLine(circle.Radius); // 4 
ChangeRadiusFromOutside(ref circle, 2); // circle est passé par référence 

// Dans le corps de ChangeRadiusFromOutside(), Console.WriteLine(circle.Radius) affiche 4 

Avec ref readonly, l’argument circle est passé par référence toutefois le compilateur effectue des optimisations en sachant que circle ne peut être modifié dans le corps de la méthode. circle.SetNewRadius() ne modifie pas l’objet circle. Le résultat est toujours 4 quand on essaie de modifier circle à l’intérieur de ChangeRadiusFromOutside().
La valeur de Radius n’est pas modifiée à cause d’une defensive copy effectuée par le compilateur pour éviter que la structure ne soit modifiée dans le corps de ChangeRadiusFromOutside(). Cette copie peut être couteuse en performance, c’est pourquoi il est préférable d’utiliser ref readonly avec des objets immutables comme readonly struct.

Utiliser ref readonly avec des objets de type valeur mutables est plus couteux en performance

Il existe une différence entre utiliser ref et ref readonly avec des structures mutables. Pour garantir que la structure mutable déclarée avec ref readonly n’est pas modifiée, le compilateur effectue une copie par valeur de la structure pour chaque déclaration d’une variable ref readonly (i.e. defensive copy). Cette copie est effectuée si la structure est mutable (c’est-à-dire qu’elle n’est pas déclarée avec readonly struct).
La copie de la structure peut être évitée si la structure est immutable en la déclarant avec readonly struct. Dans ce cas, le compilateur effectue des optimisations en évitant d’effectuer des copies par valeur à chaque déclaration d’une variable ref readonly.

Par exemple, la structure Circle est mutable:

var circle = new Circle();
ref readonly var circleRef = ref circle; // Une copie est effectuée

Si Circle est immutable:

readonly struct ImmutableCircle
{
  public readonly int Radius { get; }

  public ImmutableCircle(int radius)
  {
    Radius = radius;
  }

  void ChangeRadiusFromInside(int radius)
  {
    // this.Radius = radius;  // Provoque une erreur de compilation
  }
}

// ...
var immutableCircle = new ImmutableCircle(4);
ref readonly var circleRef = ref immutableCircle; // OK, le compilateur effectue une optimisation

ref readonly et in ont le même objectif fonctionnel, empêcher qu’un argument ne soit modifié dans le corps d’une méthode. La différence est que des messages de warning sont émis différemment par le compilateur suivant les différentes implémentations possibles.

Par exemple le code MSIL correspondant aux méthodes suivantes est identique:

public static void ChangeRadiusFromOutside(ref readonly Circle circle,
  int newRadius)
{
    circle.SetRadiusFromInside(newRadius);
}

public static void ChangeRadiusFromOutsideWithIn(in Circle circle,
  int newRadius)
{
    circle.SetRadiusFromInside(newRadius);
}

Si on effectue les appels suivants, des messages de warning différents seront émis:

var circle = new CollectionExpressions.Circle(5);

// Appels à un argument déclaré avec ref readonly 	
// L'appel suivant produit le message de warning suivant:
// warning CS9192: Argument 1 should be passed with 'ref' or 'in' keyword
CollectionExpressions.ChangeRadiusFromOutside(circle, 10);     
CollectionExpressions.ChangeRadiusFromOutside(ref circle, 10);     // Pas de warning
CollectionExpressions.ChangeRadiusFromOutside(in circle, 10);       // Pas de warning

// Appels à un argument déclaré avec in
CollectionExpressions.ChangeRadiusFromOutsideWithIn(circle, 10);       // Pas de warning
// L'appel suivant produit le message de warning suivant: 
// warning CS9191: The 'ref' modifier for argument 1 corresponding to 'in' parameter is equivalent to 'in'. 
// Consider using 'in' instead.
CollectionExpressions.ChangeRadiusFromOutsideWithIn(ref circle, 10);
CollectionExpressions.ChangeRadiusFromOutsideWithIn(in circle, 10);        // Pas de warning

Tous ces appels génèrent le même code MSIL.

Attribut “Experimental”

Cette fonctionnalité permet d’indiquer du code qui est expérimental. Si du code expérimental est utilisé, par défaut, une erreur de compilation est générée sauf si ce code est appelé par un autre code qui est aussi expérimental. Pour indiquer que du code est expérimental, il suffit d’utiliser l’attribut: System.Diagnostics.CodeAnalysis.ExperimentalAttribute

Cet attribut peut être appliqué sur une méthode, classe, structure, interface, enum, delegate, propriété ou au niveau d’une assembly entière.

Syntaxe

Le constructeur de l’attribut permet de renseigner un identifiant sous la forme d’une chaîne de caractères qui sera indiqué dans l’éventuelle erreur de compilation, par exemple si on applique cet attribut sur une classe:

[Experimental("ExperimentalClass")]
internal class ExperimentalFeature
{

}

L’identifiant ne doit pas comporter de caractères d’espacement ou des caractères spéciaux. Si l’identifiant excède 8 caractères, il sera tronqué dans l’erreur de compilation.

Si on essaie d’utiliser cette classe à partir de code qui n’est pas expérimental comme par exemple:

internal class FeatureConsumer
{
    public void ExecuteMe()
    {
        var feature = new ExperimentalFeature();    // ERREUR à la compilation
    }
}

L’erreur de compilation sera:

DoNotUs'CS12.ExperimentalFeature' is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. 

Comme on peut le voir, l’identifiant DoNotUse comportant plus de 8 caractères est tronqué.

Si la classe FeatureConsumer ou la méthode ExecuteMe() comporte l’attribut Experimental, il n’y aura plus d’erreur de compilation:

[Experimental("CanUse")]
internal class FeatureConsumer
{
    //[Experimental("CanUse")]
    public void ExecuteMe()
    {
        var feature = new ExperimentalFeature();    // OK
    }
}

Il n’est pas nécessaire que l’identifiant de compilation soit le même.

Par défaut, il y a un lien dans l’erreur de compilation qui renvoie la page learn.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-messages/feature-version-errors?f1url=%3FappId%3Droslyn%26k%3Dk(CS9204).

Si on indique une URL dans l’attribut:

[Experimental("DoNotUse", UrlFormat = "http://microsoft.com")]

Le lien sera "http://microsoft.com" plutôt que celui par défaut.

Supprimer l’erreur de compilation

On peut supprimer l’erreur de compilation en utilisant une directive de préprocesseur:

#pragma warning disable <identifiant>

Par exemple pour l’exemple précédent:

#pragma warning disable DoNotUse
internal class FeatureConsumer
{
    public void ExecuteMe()
    {
        var feature = new ExperimentalFeature();  // OK
    }
}

Lorsque le préprocesseur parcourt le code ligne par ligne, dès qu’il atteint la ligne #pragma, il ne générera plus l’erreur de compilation. Si du code se trouve avant la directive #pragma, le compilateur générera une erreur, par exemple:

internal class FeatureConsumer
{
    public void ExecuteMe()
    {
        var feature = new ExperimentalFeature();   // ERREUR de compilation
    }

#pragma warning disable DoNotUse
}

On peut restaurer la prise en compte de l’erreur de compilation avec:

#pragma warning restore <identifiant>

Par exemple:

internal class FeatureConsumer
{
    public void ExecuteMe()
    {
#pragma warning disable DoNotUse
        var feature = new ExperimentalFeature();      // OK
#pragma warning restore DoNotUse
        var feature2 = new ExperimentalFeature();     // ERREUR de compilation
    }
}

Indiquer l’attribut “Experimental” au niveau d’une assembly

On peut indiquer l’attribut Experimental à un niveau plus global au niveau d’une assembly entière en indiquant en tête d’un fichier .cs compilé (par exemple, le fichier Properties.cs):

[assembly: ExperimentalAttribute("Id")]

Autre fonctionnalité

Les “tableaux en ligne” (i.e. inline arrays) sont présentés dans un article séparé.

Références

Leave a Reply