Expressions de collection – Référence du langage C#

Vous pouvez utiliser une expression de collection pour créer des valeurs de collection courantes. Une expression de collection est une syntaxe terse qui, lorsqu’elle est évaluée, peut être attribuée à de nombreux types de collection différents. Une expression de collection contient une séquence d’éléments entre [ et ] crochets. L’exemple suivant déclare une System.Span<T> d’éléments string et les initialise aux jours de la semaine :

Span<string> weekDays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
foreach (var day in weekDays)
{
    Console.WriteLine(day);
}

Une expression de collection peut être convertie en de nombreux types de collection différents. Le premier exemple montre comment initialiser une variable à l’aide d’une expression de collection. Le code suivant montre la plupart des autres emplacements où vous pouvez utiliser une expression de collection :

// Initialize private field:
private static readonly ImmutableArray<string> _months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];

// property with expression body:
public IEnumerable<int> MaxDays =>
    [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];

public int Sum(IEnumerable<int> values) =>
    values.Sum();

public void Example()
{
    // As a parameter:
    int sum = Sum([1, 2, 3, 4, 5]);
}

Vous ne pouvez pas utiliser une expression de collection où une constante au moment de la compilation est attendue, telle que l’initialisation d’une constante ou la valeur par défaut d’un argument de méthode.

Les deux exemples précédents utilisaient des constantes comme éléments d’une expression de collection. Vous pouvez également utiliser des variables pour les éléments, comme indiqué dans l’exemple suivant :

string hydrogen = "H";
string helium = "He";
string lithium = "Li";
string beryllium = "Be";
string boron = "B";
string carbon = "C";
string nitrogen = "N";
string oxygen = "O";
string fluorine = "F";
string neon = "Ne";
string[] elements = [hydrogen, helium, lithium, beryllium, boron, carbon, nitrogen, oxygen, fluorine, neon];
foreach (var element in elements)
{
    Console.WriteLine(element);
}

Élément Spread

Vous utilisez un élément de propagation .. aux valeurs de collection inline dans une expression de collection. L’exemple suivant crée une collection pour l’alphabet complet en combinant une collection de voyelles, une collection des consonnes et la lettre « y », qui peut être :

string[] vowels = ["a", "e", "i", "o", "u"];
string[] consonants = ["b", "c", "d", "f", "g", "h", "j", "k", "l", "m",
                       "n", "p", "q", "r", "s", "t", "v", "w", "x", "z"];
string[] alphabet = [.. vowels, .. consonants, "y"];

L’élément de propagation ..vowels, lorsqu’il est évalué, produit cinq éléments : "a", "e", "i", "o"et "u". L’élément de propagation ..consonants produit 20 éléments, le nombre dans le groupeconsonants. La variable d’un élément de propagation doit être énumérable à l’aide d’une instruction foreach. Comme illustré dans l’exemple précédent, vous pouvez combiner des éléments répartis avec des éléments individuels dans une expression de collection.

Conversions

Une expression de collection peut être convertie en différents types de collection, notamment :

Important

Une expression de collection crée toujours une collection qui comprend tous les éléments de l’expression de collection, quel que soit le type cible de la conversion. Par exemple, lorsque la cible de la conversion est System.Collections.Generic.IEnumerable<T>, le code généré évalue l’expression de collection et stocke les résultats dans une collection en mémoire.

Ce comportement est distinct de celui de LINQ, où une séquence peut ne pas être instanciée tant qu’elle n’est pas énumérée. Vous ne pouvez pas utiliser d’expressions de collection pour générer une séquence infinie qui ne sera pas énumérée.

Le compilateur utilise l’analyse statique pour déterminer le moyen le plus performant de créer la collection déclarée avec une expression de collection. Par exemple, l’expression de collection vide [] peut être écrite sous la forme Array.Empty<T>() à condition que la cible ne soit pas modifiée après l’initialisation. Quand la cible est un System.Span<T> ou un System.ReadOnlySpan<T>, le stockage peut être alloué par la pile. La spécification de la fonctionnalité des expressions de collection spécifie les règles que le compilateur doit suivre.

De nombreuses API sont surchargées avec plusieurs types de collection en tant que paramètres. Étant donné qu’une expression de collection peut être convertie en divers types d’expression différents, ces API peuvent nécessiter des casts au niveau de l’expression de collection pour spécifier la conversion correcte. Les règles de conversion suivantes résolvent certaines ambiguïtés :

  • La conversion en Span<T>, ReadOnlySpan<T>ou un autre type ref struct est préférable à une conversion vers un type de struct non ref.
  • La conversion en type noninterface est préférable à une conversion en type d’interface.

Lorsqu’une expression de collection est convertie en Span ou ReadOnlySpan, le contexte sécurisé de l’objet span est extrait du contexte sécurisé de tous les éléments inclus dans l’étendue. Pour obtenir des règles détaillées, consultez la spécification d’expression Collection.

Générateur de collection

Les expressions de collection fonctionnent avec n’importe quel type de collection à comportement adéquat. Une collection à comportement adéquat présente les caractéristiques suivantes :

  • La valeur de Count ou de Length dans une collection countable produit la même valeur que le nombre d’éléments lorsque ceux-ci sont énumérés.
  • Les types dans l’espace de noms System.Collections.Generic sont censés ne pas avoir d’effets secondaires. Ainsi, le compilateur peut optimiser les scénarios dans lesquels de tels types pourraient être utilisés comme valeurs intermédiaires, mais pas lorsqu’ils sont exposés.
  • Un appel à un membre .AddRange(x) applicable dans une collection a pour résultat une valeur identique à celle produite par une itération sur x et par l’ajout de toutes ses valeurs énumérées individuellement à la collection avec .Add.

Tous les types de collection du runtime .NET ont un comportement adéquat.

Avertissement

Si un type de collection personnalisé n’a pas un comportement adéquat, l’utilisation du type de collection avec des expressions de collection génère un comportement indéfini.

Vos types optent pour la prise en charge des expressions de collection en écrivant une méthode Create() et en appliquant le System.Runtime.CompilerServices.CollectionBuilderAttribute au type de collection pour indiquer la méthode du générateur. Par exemple, considérez une application qui utilise des mémoires tampons de longueur fixe de 80 caractères. Cette classe pourrait ressembler au code suivant :

public class LineBuffer : IEnumerable<char>
{
    private readonly char[] _buffer = new char[80];

    public LineBuffer(ReadOnlySpan<char> buffer)
    {
        int number = (_buffer.Length < buffer.Length) ? _buffer.Length : buffer.Length;
        for (int i = 0; i < number; i++)
        {
            _buffer[i] = buffer[i];
        }
    }

    public IEnumerator<char> GetEnumerator() => _buffer.AsEnumerable<char>().GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator() => _buffer.GetEnumerator();

    // etc
}

Vous souhaitez l’utiliser avec des expressions de collection, comme indiqué dans l’exemple suivant :

LineBuffer line = ['H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd', '!'];

Le LineBuffertype d’impléments IEnumerable<char>, de sorte que le compilateur le reconnaît comme une collection d’éléments char. Le paramètre de type de l’interface System.Collections.Generic.IEnumerable<T> implémentée indique le type d’élément. Vous devez ajouter deux ajouts à votre application pour pouvoir attribuer des expressions de collection à un objet LineBuffer. Tout d’abord, vous devez créer une classe qui contient une méthode Create :

internal static class LineBufferBuilder
{
    internal static LineBuffer Create(ReadOnlySpan<char> values) => new LineBuffer(values);
}

La méthode Create doit retourner un objet LineBuffer, et elle doit prendre un paramètre unique du type ReadOnlySpan<char>. Le paramètre de type du ReadOnlySpan doit correspondre au type d’élément de la collection. Une méthode de générateur qui retourne une collection générique aurait le ReadOnlySpan<T> générique comme paramètre. La méthode doit être accessible et static.

Enfin, vous devez ajouter le CollectionBuilderAttribute à la LineBuffer déclaration de classe  :

[CollectionBuilder(typeof(LineBufferBuilder), "Create")]

Le premier paramètre fournit le nom de la classe Builder. Le deuxième attribut fournit le nom de la méthode du générateur.