Expresiones de colecciones: referencia del lenguaje C#

Puede usar una expresión de colección para crear valores de colección comunes. Una expresión de colección es una sintaxis tersa que, cuando se evalúa, se puede asignar a muchos tipos de colección diferentes. Una expresión de colección contiene una secuencia de elementos entre corchetes [ y ]. En el ejemplo siguiente se declara un System.Span<T> de elementos string y se inicializan los días de la semana:

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

Una expresión de colección se puede convertir en muchos tipos de colección diferentes. En el primer ejemplo se muestra cómo inicializar una variable mediante una expresión de colección. En el código siguiente se muestran muchas otras situaciones en las que puede usar una expresión de colección:

// 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]);
}

No se puede usar una expresión de colección cuando se espera una constante en tiempo de compilación (p. ej., al inicializar una constante o como el valor predeterminado para un argumento de método).

Ambos ejemplos anteriores usaron constantes como elementos de una expresión de colección. También puede usar variables para los elementos como se muestra en el ejemplo siguiente:

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);
}

Elemento de propagación

El elemento de propagación.. se usa para insertar los valores de colección en una expresión de colección. En el ejemplo siguiente se crea una colección para que contenga el alfabeto completo mediante la combinación de una colección de vocales, una colección de los consonantes y la letra "y" por separado:

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"];

Al evaluarse, el elemento de propagación ..vowels, genera cinco elementos: "a", "e", "i", "o" y "u". El elemento de propagación ..consonants genera 20 elementos, que es el tamaño de la matriz consonants. La variable de un elemento de propagación debe enumerarse mediante una instrucción foreach. Como se muestra en el ejemplo anterior, puede combinar elementos distribuidos con elementos individuales en una expresión de colección.

Conversiones

Una expresión de colección se puede convertir en muchos tipos de colección diferentes, incluidos los siguientes:

Importante

Una expresión de colección siempre crea una colección que incluye todos los elementos de la expresión de colección, independientemente del tipo de destino de la conversión. Por ejemplo, cuando el destino de la conversión es System.Collections.Generic.IEnumerable<T>, el código generado evalúa la expresión de colección y almacena los resultados en una colección en memoria.

Este comportamiento es distinto de LINQ, donde es posible que no se cree una instancia de una secuencia hasta que se enumere. No se pueden usar expresiones de colección para generar una secuencia infinita que no va a enumerar.

El compilador usa análisis estáticos para determinar la manera más eficaz de crear la colección declarada con una expresión de colección. Por ejemplo, la expresión de colección vacía, [], se puede realizar como Array.Empty<T>() si el destino no se modificara después de la inicialización. Cuando el destino es System.Span<T> o System.ReadOnlySpan<T>, el almacenamiento se puede asignar a la pila. La especificación de características de expresiones de colección especifica las reglas que debe seguir el compilador.

Muchas API se sobrecargan con varios tipos de colección como parámetros. Como una expresión de colección se puede convertir en muchos tipos de expresiones diferentes, estas API pueden requerir conversiones en la expresión de colección para especificar la conversión correcta. Las siguientes reglas de conversión resuelven algunas de estas ambigüedades:

  • La conversión a Span<T>, ReadOnlySpan<T> u otro tipo de ref struct es mejor que una conversión a un tipo de estructura que no sea ref.
  • La conversión a un tipo que no sea interfaz es mejor que una conversión a un tipo de interfaz.

Cuando una expresión de colección se convierte en Span o ReadOnlySpan, el contexto seguro del objeto span se toma del contexto seguro de todos los elementos incluidos en el intervalo. Para obtener reglas detalladas, consulte la especificación de la expresión de colección.

Generador de colecciones

Las expresiones de colección funcionan con cualquier tipo de colección con un buen comportamiento. Una asociación con un buen comportamiento tiene las siguientes características:

  • El valor de Count o Length en una colección contable genera el mismo valor que el número de elementos cuando se enumeran.
  • Se supone que los tipos del espacio de nombres System.Collections.Generic no tienen efectos secundarios. De este modo, el compilador puede optimizar los escenarios donde estos tipos se utilicen como valores intermedios, pero no se expongan.
  • Una llamada a algún miembro de .AddRange(x) aplicable en una colección dará como resultado el mismo valor final que la iteración en x y la adición de todos sus valores enumerados individualmente a la colección con .Add.

Todos los tipos de colección del entorno de ejecución de .NET tienen un buen comportamiento.

Advertencia

Si un tipo de colección personalizado no tiene un buen comportamiento, el comportamiento cuando se usa ese tipo de colección con expresiones de colección no está definido.

Sus tipos optan por admitir expresiones de colección al escribir un método Create() y aplicar el System.Runtime.CompilerServices.CollectionBuilderAttribute en el tipo de colección para indicar el método generador. Por ejemplo, considere una aplicación que usa búferes de longitud fija de 80 caracteres. Esa clase tendría un aspecto similar al código siguiente:

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
}

Es útil usarla con expresiones de colección, como se muestra en el ejemplo siguiente:

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

El tipo LineBuffer implementa IEnumerable<char>, por lo que el compilador reconoce eso como una colección de elementos de char. El parámetro de tipo de la interfaz implementada System.Collections.Generic.IEnumerable<T> indica el tipo de elemento. Debe añadir dos cosas a la aplicación para poder asignar expresiones de colección a un objeto LineBuffer. En primer lugar, debe crear una clase que contenga un método Create:

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

El método Create debe devolver un objeto LineBuffer y debe tomar un único parámetro del tipo ReadOnlySpan<char>. El parámetro de tipo de ReadOnlySpan debe coincidir con el tipo de elemento de la colección. Un método de generador que devuelve una colección genérica tendría el genérico ReadOnlySpan<T> como parámetro. El método debe ser accesible y static.

Para terminar, agregue CollectionBuilderAttribute a la declaración de clase LineBuffer:

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

El primer parámetro proporciona el nombre de la clase de generador. El segundo atributo proporciona el nombre del método de generador.