
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#
Chronologie des releases
Lien entre la version C# et le compilateur
Limiter la version C# à compiler
Fonctionnalités C# 12
Primary constructors
Collection expressions
Instancier une collection directement avec les éléments entre crochets
Opérateur spread
ref readonly en argument de méthode
Avant C# 12
A partir de C# 12
Attribut “Experimental”
Syntaxe
Supprimer l’erreur de compilation
Indiquer l’attribut “Experimental” au niveau d’une assembly
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
- Sur Linux:
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ètreLangVersion
:<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 unrecord
, - 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 unestruct
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’exempleList<int>
). Cela signifie qu’il n’est pas possible d’utiliser l’opérateurvar
: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
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 commeint
,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 ) |
|
Par exemple:
|
Manipulation d’une variable locale ( ref ) |
|
Par exemple:
Réaffectation d’une référence:
Par exemple:
|
Manipulation d’une variable locale en lecture seule ( ref readonly ) |
|
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:
ATTENTION: Pour éviter les defensive copies, il est préférable que la structure soit immutable (cf. |
Retour de fonction par référence ( return ref ) |
|
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:
Par contre, on ne peut pas retourner une variable locale d’une fonction:
|
Retour de fonction par référence en lecture seule ( return ref ) |
|
Même restriction que pour un retour de fonction par référence simple. Un membre en readonly doit obligatoirement être retourné en readonly :
L’affectation dans une variable d’une fonction en
ATTENTION: Pour éviter les defensive copies, il est préférable que la structure soit immutable (cf. |
Indiquer qu’un argument de méthode est en lecture seule avec in |
|
L’affectation d’un membre d’un argument avec in n’est pas possible.Par exemple:
ATTENTION: Pour éviter les defensive copies, il est préférable que la structure soit immutable (cf. |
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
.
ref readonly
avec des objets de type valeur mutables est plus couteux en performanceIl 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é.
- What’s new in C# 12: https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-12
- .NET compiler platform package version reference: https://learn.microsoft.com/en-us/visualstudio/extensibility/roslyn-version-support?view=vs-2022
- Microsoft.Net.Compilers.Toolset : https://www.nuget.org/packages/Microsoft.Net.Compilers.Toolset/
- Primary constructors:
- Primary constructors: https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/instance-constructors#primary-constructors
- Declare primary constructors for classes and structs: https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/tutorials/primary-constructors
- Collection expressions:
- Indices and ranges: https://learn.microsoft.com/en-us/dotnet/csharp/tutorials/ranges-indexes
- Collection expressions – C# language reference: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/collection-expressions
- Member access operators and expressions – the dot, indexer, and invocation operators.: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/member-access-operators#index-from-end-operator-
- Range Struct: https://learn.microsoft.com/fr-fr/dotnet/api/system.range?view=net-9.0
ref readonly
en argument de méthode:- ref readonly modifier: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/method-parameters#ref-readonly-modifier
- ref readonly parameters: https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/ref-readonly-parameters.md
- Mot-clé
in
: https://cdiese.fr/csharp7-value-type-object-by-reference-valeur-par-reference/#cs7-value_type_by_ref-in_keyword
- Attribut
Experimental
:- Experimental attributes: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/attributes/general#experimental-attributes
- Directives de préprocesseur C#: https://learn.microsoft.com/fr-fr/dotnet/csharp/language-reference/preprocessor-directives
- ExperimentalAttribute: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-12.0/experimental-attribute
- ExperimentalAttribute Class: https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.codeanalysis.experimentalattribute