Partilhar via


Expressões de coleção - referência da linguagem C#

Use uma expressão de coleção para criar valores comuns de coleção. Uma expressão de coleção é uma sintaxe concisa que pode atribuir a muitos tipos diferentes de coleções. Uma expressão de coleção contém uma sequência de elementos entre [ colchetes e colchetes ] .

A referência da linguagem C# documenta a versão mais recentemente lançada da linguagem C#. Contém também documentação inicial para funcionalidades em versões preliminares públicas para a próxima versão da linguagem.

A documentação identifica qualquer funcionalidade introduzida pela primeira vez nas últimas três versões da língua ou em pré-visualizações públicas atuais.

Sugestão

Para saber quando uma funcionalidade foi introduzida pela primeira vez em C#, consulte o artigo sobre o histórico de versões da linguagem C#.

O exemplo a seguir declara um System.Span<T> dos string elementos e os inicializa para os dias da semana:

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

Pode converter uma expressão de coleção para muitos tipos de coleção diferentes. O primeiro exemplo demonstrou como inicializar uma variável usando uma expressão de coleção. O código a seguir mostra muitos dos outros locais onde você pode usar uma expressão de coleção:

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

Não pode usar uma expressão de coleção onde se espera uma constante em tempo de compilação, como ao inicializar uma constante, ou como valor padrão para um argumento de método.

Ambos os exemplos anteriores usavam constantes como elementos de uma expressão de coleção. Também pode usar variáveis para os elementos, como mostrado no seguinte exemplo:

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 propagação

Use um elemento .. para inlinear valores de coleção numa expressão de coleção. O exemplo a seguir cria uma coleção para o alfabeto completo combinando uma coleção de vogais, uma coleção de consoantes e a letra "y", que pode ser:

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

O elemento ..vowelsspread , quando avaliado, produz cinco elementos: "a", "e", "i", "o", e "u". O elemento ..consonants spread produz 20 elementos, o número na consonants matriz. A expressão num elemento de espalhamento deve ser enumerável usando uma foreach afirmação. Como mostrado no exemplo anterior, você pode combinar elementos spread com elementos individuais em uma expressão de coleção.

Conversões

Pode converter uma expressão de coleção para diferentes tipos de coleção, incluindo:

Observação

Não podes usar expressões de coleção para inicializar arrays inline. As matrizes embutidas exigem sintaxe de inicialização diferente.

Importante

Uma expressão de coleção sempre cria uma coleção que inclui todos os elementos na expressão de coleção, independentemente do tipo de destino da conversão. Por exemplo, quando o destino da conversão é System.Collections.Generic.IEnumerable<T>, o código gerado avalia a expressão de coleção e armazena os resultados em uma coleção na memória.

Esse comportamento é distinto do LINQ, onde uma sequência pode não ser instanciada até que seja enumerada. Não é possível usar expressões de coleção para gerar uma sequência infinita que não será enumerada.

O compilador usa análise estática para determinar a maneira mais eficiente de criar a coleção declarada com uma expressão de coleção. Por exemplo, a expressão de coleção vazia, [], pode ser percebida como Array.Empty<T>() se o destino não fosse modificado após a inicialização. Quando o destino é um System.Span<T> ou System.ReadOnlySpan<T>, o armazenamento pode ser alocado em pilha. A especificação do recurso de expressões de coleção especifica as regras que o compilador deve seguir.

Muitas APIs são sobrecarregadas com vários tipos de coleção como parâmetros. Como uma expressão de coleção pode ser convertida em muitos tipos de expressão diferentes, essas APIs podem exigir versões na expressão de coleção para especificar a conversão correta. As seguintes regras de conversão resolvem algumas das ambiguidades:

  • Uma melhor conversão de elementos é preferível a uma melhor conversão de tipo de coleção. Em outras palavras, o tipo de elementos na expressão da coleção tem mais importância do que o tipo da coleção. Essas regras são descritas na especificação do recurso para uma melhor conversão da expressão de coleção.
  • A conversão para Span<T>, ReadOnlySpan<T>, ou outro ref struct tipo é melhor do que uma conversão para um tipo struct não-ref.
  • A conversão para um tipo sem interface é melhor do que uma conversão para um tipo de interface.

Quando converte uma expressão de coleção para um Span ou ReadOnlySpan, o contexto seguro do objeto span provém do contexto seguro de todos os elementos incluídos no span. Para obter regras detalhadas, consulte a especificação de expressão de coleção.

Construtor de coleções

As expressões de coleção funcionam com qualquer tipo de coleção que seja bem comportado. Uma coleção bem comportada tem as seguintes características:

  • O valor de Count ou Length em uma coleção contável produz o mesmo valor que o número de elementos quando enumerado.
  • Os tipos no System.Collections.Generic namespace são livres de efeitos secundários. O compilador pode otimizar cenários onde estes tipos podem ser usados como valores intermédios, mas não os expõe de outra forma.
  • Uma chamada a um membro aplicável .AddRange(x) numa coleção resulta no mesmo valor final que iterar e x adicionar todos os seus valores enumerados individualmente à coleção usando .Add.

Todos os tipos de coleção no tempo de execução do .NET são bem comportados.

Aviso

Se um tipo de coleção personalizada não se comporta bem, o comportamento é indefinido quando se usa esse tipo de coleção com expressões de coleção.

Os seus tipos optam pelo suporte à expressão de coleção escrevendo um Create() método e aplicando o System.Runtime.CompilerServices.CollectionBuilderAttribute atributo no tipo de coleção para indicar o método construtor. Por exemplo, considere um aplicativo que usa buffers de comprimento fixo de 80 caracteres. Essa classe pode se parecer com o seguinte código:

public class LineBuffer : IEnumerable<char>
{
    private readonly char[] _buffer;
    private readonly int _count;

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

    public int Count => _count;
    
    public char this[int index]
    {
        get
        {
            if (index >= _count)
                throw new IndexOutOfRangeException();
            return _buffer[index];
        }
    }

    public IEnumerator<char> GetEnumerator()
    {
        for (int i = 0; i < _count; i++)
        {
            yield return _buffer[i];
        }
    }
    
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

    // etc
}

Deve usá-lo com expressões de coleção, como mostrado no seguinte exemplo:

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

O LineBuffer tipo implementa IEnumerable<char>, para que o compilador o reconheça como uma coleção de char itens. O parâmetro type da interface implementada System.Collections.Generic.IEnumerable<T> indica o tipo de elemento. Você precisa fazer duas adições ao seu aplicativo para poder atribuir expressões de coleção a um LineBuffer objeto. Primeiro, você precisa criar uma classe que contenha um Create método:

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

O Create método deve devolver um LineBuffer objeto, e deve assumir um parâmetro final do tipo ReadOnlySpan<char>. O parâmetro type do ReadOnlySpan deve corresponder ao tipo de elemento da coleção. Um método construtor que devolve uma coleção genérica tem o genérico ReadOnlySpan<T> como parâmetro. O método deve ser acessível e static.

A partir de C# 15, o Create método pode ter parâmetros adicionais antes do ReadOnlySpan<T> parâmetro. Pode passar valores a estes parâmetros usando um with(...) elemento na expressão de coleção. Consulte os argumentos do construtor de coleções para mais detalhes.

Finalmente, você deve adicionar o CollectionBuilderAttribute à declaração de LineBuffer classe:

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

O primeiro parâmetro fornece o nome da classe Builder . O segundo atributo fornece o nome do método builder.

Argumentos de expressão de coleções

A partir de C# 15, pode passar argumentos ao construtor ou método de fábrica da coleção subjacente usando um with(...) elemento como primeiro elemento numa expressão de coleção. Esta funcionalidade permite-lhe especificar capacidade, comparadores ou outros parâmetros do construtor diretamente dentro da sintaxe da expressão da coleção. Para mais informações, consulte a especificação de características de argumentos de expressão de coleção.

O with(...) elemento deve ser o primeiro elemento na expressão da coleção. Os argumentos declarados no with(...) elemento são passados ao construtor ou método de criação apropriado com base no tipo alvo. Podes usar qualquer expressão válida para os argumentos no with elemento.

Argumentos de construtores

Quando o tipo alvo é uma classe ou struct que implementa System.Collections.IEnumerable, os argumentos em with(...) são avaliados e os resultados são passados ao construtor. O compilador usa resolução de sobrecarga para selecionar o construtor de melhor correspondência:

public void CollectionArgumentsExamples()
{
    string[] values = ["one", "two", "three"];

    // Pass capacity argument to List<T> constructor
    List<string> names = [with(capacity: values.Length * 2), .. values];

    // Pass comparer argument to HashSet<T> constructor
    HashSet<string> set = [with(StringComparer.OrdinalIgnoreCase), "Hello", "HELLO", "hello"];
    // set contains only one element because all strings are equal with OrdinalIgnoreCase

    // Pass capacity to IList<T> (uses List<T> constructor)
    IList<int> numbers = [with(capacity: 100), 1, 2, 3];
}

No exemplo anterior:

Argumentos do construtor de coleções

Para tipos com a System.Runtime.CompilerServices.CollectionBuilderAttribute, os argumentos declarados no with(...) elemento são avaliados e os resultados são passados ao método create antes do ReadOnlySpan<T> parâmetro. Esta funcionalidade permite criar métodos para aceitar parâmetros de configuração:

internal static class MySetBuilder
{
    internal static MySet<T> Create<T>(ReadOnlySpan<T> items) => new MySet<T>(items);
    internal static MySet<T> Create<T>(IEqualityComparer<T> comparer, ReadOnlySpan<T> items) => 
        new MySet<T>(items, comparer);
}

Pode então usar o with(...) elemento para passar o comparador:

public void CollectionBuilderArgumentsExample()
{
    // Pass comparer to a type with CollectionBuilder attribute
    // The comparer argument is passed before the ReadOnlySpan<T> parameter
    MySet<string> mySet = [with(StringComparer.OrdinalIgnoreCase), "A", "a", "B"];
    // mySet contains only two elements: "A" and "B"
}

O método create é selecionado usando resolução de sobrecarga com base nos argumentos fornecidos. O ReadOnlySpan<T> que contém os elementos da coleção é sempre o último parâmetro.

Tipos de alvos de interface

Vários tipos de alvos de interface suportam argumentos de expressão de coleção. A tabela seguinte mostra as interfaces suportadas e as suas assinaturas de construtores aplicáveis:

Interfaz Elementos suportados with
IEnumerable<T>, IReadOnlyCollection<T>, IReadOnlyList<T> () (apenas vazio)
ICollection<T>, IList<T> (), (int capacity)

Para IList<T> e ICollection<T>, o compilador usa um System.Collections.Generic.List<T> com o construtor especificado.

Restrições

O with(...) elemento tem as seguintes restrições:

  • Deve ser o primeiro elemento na expressão da coleção.
  • Discussões não podem ter dynamic tipo.
  • Não é suportado para arrays ou tipos de expansão (Span<T>, ReadOnlySpan<T>).