Expressões de coleção - Referência de linguagem C#
Você pode usar uma expressão de coleção para criar valores comuns de coleção. Uma expressão de coleção é uma sintaxe concisa que, quando avaliada, pode ser atribuída a muitos tipos de coleção diferentes. Uma expressão de coleção contém uma sequência de elementos entre colchetes [
e ]
. O exemplo a seguir declara uma System.Span<T> de elementos string
e os inicializa nos dias da semana:
Span<string> weekDays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
foreach (var day in weekDays)
{
Console.WriteLine(day);
}
Uma expressão de coleção pode ser convertida em vários 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]);
}
Você não pode usar uma expressão de coleção em que uma constante de tempo de compilação é esperada, como inicializar uma constante, ou como o valor padrão para um argumento de método.
Ambos os exemplos anteriores usaram constantes como os elementos de uma expressão de coleção. Você também pode usar variáveis para os elementos, conforme mostrado no exemplo a seguir:
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
Você usa um elemento de espalhamento ..
para embutir valores de coleção em uma 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 de propagação ..vowels
, quando avaliado, produz cinco elementos: "a"
, "e"
, "i"
, "o"
e "u"
. O elemento de propagação ..consonants
produz 20 elementos, o número na matriz consonants
. A variável em um elemento de propagação deve ser enumerável usando uma instrução foreach
. Como mostrado no exemplo anterior, você pode combinar elementos de propagação com elementos individuais em uma expressão de coleção.
Conversões
Uma expressão coleção pode ser convertida em diferentes tipos de coleção, incluindo:
- System.Span<T> e System.ReadOnlySpan<T>.
- Matrizes.
- Qualquer tipo com um método criar cujo tipo de parâmetro é
ReadOnlySpan<T>
onde há uma conversão implícita do tipo de expressão de coleção paraT
. - Qualquer tipo que ofereça suporte a um inicializador de coleção, como System.Collections.Generic.List<T>. Normalmente, esse requisito significa que o tipo oferece suporte a System.Collections.Generic.IEnumerable<T> e há um método
Add
acessível para adicionar itens à coleção. Deve haver uma conversão implícita do tipo de elementos de expressão da coleção para o tipo de elemento da coleção. Para elementos de propagação, deve haver uma conversão implícita do tipo do elemento de propagação para o tipo de elemento da coleção. - Qualquer das seguintes interfaces:
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 da consulta integrada à linguagem (LINQ), em que uma sequência pode não ser instanciada até ser enumerada. Você não pode 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 realizada como Array.Empty<T>() se o destino não fosse modificado após a inicialização. Quando o destino é 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 coerções na expressão de coleção para especificar a conversão correta. As seguintes regras de conversão resolvem algumas das ambiguidades:
- A conversão para Span<T>, ReadOnlySpan<T> ou outro tipo de
ref struct
é melhor do que uma conversão para um tipo de estrutura não ref. - A conversão para um tipo que não seja interface é melhor do que uma conversão para um tipo de interface.
Quando uma expressão de coleção é convertida em um Span
ou ReadOnlySpan
, o contexto seguro do objeto span é retirado do contexto seguro de todos os elementos incluídos na extensão. Para obter regras detalhadas, consulte a especificação de expressão Coleção.
Construtor de coleções
As expressões de coleção funcionam com qualquer tipo de coleção bem comportada. Uma coleção bem comportada tem as seguintes características:
- O valor de
Count
ouLength
em uma coleção contável produz o mesmo valor que o número de elementos quando enumerado. - Presume-se que os tipos no namespace System.Collections.Generic estejam livres de efeito colateral. Dessa forma, o compilador pode otimizar cenários em que esses tipos podem ser usados como valores intermediários, mas de outra forma não serão expostos.
- Uma chamada para algum membro
.AddRange(x)
aplicável em uma coleção resultará no mesmo valor final que iterar emx
e adicionar todos os seus valores enumerados individualmente à coleção com.Add
.
Todos os tipos de coleção no runtime do .NET são bem comportados.
Aviso
Se um tipo de coleção personalizado não for bem comportado, o comportamento ao usar esse tipo de coleção com expressões de coleção será indefinido.
Seus tipos aceitam o suporte à expressão de coleção escrevendo um método Create()
e aplicando o System.Runtime.CompilerServices.CollectionBuilderAttribute no tipo de coleção para indicar o método do 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 = 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
}
Você gostaria de usá-lo com expressões de coleção, conforme mostrado no exemplo a seguir:
LineBuffer line = ['H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd', '!'];
O tipo LineBuffer
implementa IEnumerable<char>
, portanto, o compilador o reconhece como uma coleção de itens char
. O parâmetro de tipo da interface System.Collections.Generic.IEnumerable<T> implementada indica o tipo de elemento. Você precisa fazer duas adições ao seu aplicativo para poder atribuir expressões de coleção a um objeto LineBuffer
. Primeiro, você precisa criar uma classe que contém um método Create
:
internal static class LineBufferBuilder
{
internal static LineBuffer Create(ReadOnlySpan<char> values) => new LineBuffer(values);
}
O método Create
deve retornar um objeto LineBuffer
e deve usar um único parâmetro do tipo ReadOnlySpan<char>
. O parâmetro de tipo do ReadOnlySpan
deve corresponder ao tipo de elemento da coleção. Um método de construtor que retorna uma coleção genérica teria o ReadOnlySpan<T>
genérico como seu parâmetro. O método deve ser acessível e static
.
Finalmente, você deve adicionar o CollectionBuilderAttribute à declaração de classe LineBuffer
:
[CollectionBuilder(typeof(LineBufferBuilder), "Create")]
O primeiro parâmetro fornece o nome da classe Construtor. O segundo atributo fornece o nome do método construtor.