Nota
O acceso a esta páxina require autorización. Pode tentar iniciar sesión ou modificar os directorios.
O acceso a esta páxina require autorización. Pode tentar modificar os directorios.
Use una expresión de colección para crear valores de colección comunes. Una expresión de colección es una sintaxis terse que se puede asignar a muchos tipos de colección diferentes. Una expresión de colección contiene una secuencia de elementos entre corchetes [ y ].
La documentación de referencia del lenguaje C# cubre la versión más reciente publicada del lenguaje C#. También contiene documentación inicial sobre las características de las versiones preliminares públicas de la próxima versión del lenguaje.
La documentación identifica cualquier característica introducida por primera vez en las últimas tres versiones del idioma o en las versiones preliminares públicas actuales.
Sugerencia
Para buscar cuándo se introdujo por primera vez una característica en C#, consulte el artículo sobre el historial de versiones del lenguaje C#.
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);
}
Puede convertir una expresión de colección 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 en la que se espera una constante en tiempo de compilación, como al inicializar una constante o como 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
Use un elemento .. para los valores de colección insertados 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 expresión de un elemento de propagación debe ser enumerable mediante una foreach instrucción . Como se muestra en el ejemplo anterior, puede combinar elementos distribuidos con elementos individuales en una expresión de colección.
Conversiones
Puede convertir una expresión de colección en diferentes tipos de colección, entre los que se incluyen:
- System.Span<T> y System.ReadOnlySpan<T>.
-
Matrices, como
int[]ostring[]. - Cualquier tipo con un método create cuyo tipo de parámetro es
ReadOnlySpan<T>donde hay una conversión implícita del tipo de expresión de colección aT. - Cualquier tipo que admita un inicializador de colección, como System.Collections.Generic.List<T>. Normalmente, este requisito significa que el tipo admite System.Collections.Generic.IEnumerable<T> y hay un método
Addaccesible para agregar elementos a la colección. Debe haber una conversión implícita del tipo de elementos de expresión de colección al tipo de elemento de la colección. Para los elementos distribuidos, debe haber una conversión implícita del tipo del elemento de propagación al tipo de elemento de la colección. - Cualquiera de las interfaces siguientes:
Nota:
No se pueden usar expresiones de colección para inicializar matrices insertadas. Las matrices insertadas requieren una sintaxis de inicialización diferente.
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 cláusula §12.8.25 Collection expressions del estándar de C# 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:
- Se prefiere una mejor conversión de elementos sobre una mejor conversión de tipos de colección. En otras palabras, el tipo de elementos de la expresión de colección tiene más importancia que el tipo de la colección. Estas reglas se describen en la mejor conversión de la cláusula expression de la especificación del lenguaje C#.
- La conversión a Span<T>, ReadOnlySpan<T> u otro tipo de
ref structes 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.
Al convertir una expresión de colección en o SpanReadOnlySpan, el contexto seguro del objeto span procede del contexto seguro de todos los elementos incluidos en el intervalo. Para obtener reglas detalladas, consulte §9.7.2 Contextos seguros ref en el estándar de C#.
Generador de colecciones
Las expresiones de colección funcionan con cualquier tipo de colección que se comporte bien. Una asociación con un buen comportamiento tiene las siguientes características:
- El valor de
CountoLengthen una colección countable genera el mismo valor que el número de elementos al enumerar la colección. - Los tipos del System.Collections.Generic espacio de nombres son libres de efectos secundarios. El compilador optimiza los escenarios en los que estos tipos se pueden usar como valores intermedios, pero no los expone de otro modo.
- Una llamada a un miembro aplicable
.AddRange(x)en una colección da como resultado el mismo valor final que la iteraciónxy la adición de todos sus valores enumerados individualmente a la colección mediante.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 se comporta bien, el comportamiento no se define cuando se usa ese tipo de colección con expresiones de colección.
Los tipos optan por admitir expresiones de colección escribiendo un Create() método y aplicando el System.Runtime.CompilerServices.CollectionBuilderAttribute atributo en el tipo de colección para indicar el método builder. 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;
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
}
Quiere usarlo 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 Create método debe devolver un LineBuffer objeto y debe tomar un parámetro final 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 tiene el genérico ReadOnlySpan<T> como parámetro. El método debe ser accesible y static.
A partir de C# 15, el Create método puede tener parámetros adicionales antes del ReadOnlySpan<T> parámetro . Puede pasar valores a estos parámetros mediante un with(...) elemento de la expresión de colección. Consulte Argumentos del generador de colecciones para obtener más información.
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.
Argumentos de expresión de colección
A partir de C# 15, puede pasar argumentos al constructor o al método factory de la colección subyacente mediante un with(...) elemento como primer elemento de una expresión de colección. Esta característica permite especificar capacidad, comparadores u otros parámetros de constructor directamente dentro de la sintaxis de la expresión de colección. Para obtener más información, consulte la especificación de características de argumentos de expresión de colección.
El with(...) elemento debe ser el primer elemento de la expresión de colección. Los argumentos declarados en el with(...) elemento van al constructor adecuado o crean métodos basados en el tipo de destino. Puede usar cualquier expresión válida para los argumentos del with elemento .
Las expresiones de argumento y las expresiones de elemento se evalúan una vez, en orden de izquierda a derecha, con los with(...) argumentos evaluados antes de los elementos de la colección. Para una propagación como .. GetItems(), las llamadas GetItems() de evaluación para obtener la colección que se va a distribuir. La enumeración es el paso independiente que lee los elementos de esa colección y los agrega al resultado. El compilador se evalúa GetItems() en su posición de izquierda a derecha, pero puede retrasar la enumeración hasta después de evaluar las expresiones de elemento posteriores. No se base en los efectos secundarios de la enumeración que se producen antes de esas expresiones posteriores.
Argumentos del constructor
Cuando el tipo de destino es una clase o estructura que implementa System.Collections.IEnumerable, los argumentos de with(...) se evalúan y los resultados van al constructor. El compilador usa la resolución de sobrecargas para seleccionar el mejor constructor coincidente:
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];
}
En el ejemplo anterior:
- El
List<string>constructor con uncapacityparámetro se llama a convalues.Length * 2. - El
HashSet<string>constructor con un System.Collections.Generic.IEqualityComparer<T> parámetro se llama a conStringComparer.OrdinalIgnoreCase. - En el caso de los tipos de destino de interfaz como System.Collections.Generic.IList<T>, el compilador crea un
List<T>con la capacidad especificada.
Para un tipo de destino construido de esta manera, with(...) es necesario si el tipo no tiene ningún constructor sin parámetros accesible. Úselo para proporcionar los argumentos a un constructor accesible. Por ejemplo, NamedValues requiere un nombre y hereda el comportamiento de la colección de List<int>:
public class NamedValues(string name) : List<int>
{
public string Name { get; } = name;
}
Esta expresión de colección proporciona el nombre necesario y, a continuación, agrega los elementos:
NamedValues readings = [with("temperature"), 18, 21, 24];
Console.WriteLine($"{readings.Name}: {string.Join(", ", readings)}");
// output: temperature: 18, 21, 24
Argumentos del generador de colecciones
Para los tipos con , System.Runtime.CompilerServices.CollectionBuilderAttributeel with(...) elemento evalúa los argumentos declarados y envía los resultados al método create antes del ReadOnlySpan<T> parámetro . Esta característica permite crear métodos para aceptar parámetros de configuración:
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);
}
Use el with(...) elemento para pasar el 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"
}
La resolución de sobrecarga selecciona el método create en función de los argumentos que proporcione. El ReadOnlySpan<T> que contiene los elementos de la colección siempre es el último parámetro.
Tipos de destino de interfaz
Varios tipos de destino de interfaz admiten argumentos de expresión de colección. En la tabla siguiente se muestran las interfaces admitidas y sus firmas de constructor aplicables:
| Interfaz | Elementos admitidos with |
|---|---|
| IEnumerable<T>, , IReadOnlyCollection<T>, IReadOnlyList<T> |
() (solo vacío) |
| ICollection<T>, IList<T> |
(), (int capacity) |
Para IList<T> y ICollection<T>, el compilador usa con System.Collections.Generic.List<T> el constructor especificado.
Restrictions
El with(...) elemento tiene las restricciones siguientes:
- Debe ser el primer elemento de la expresión de colección.
- Los argumentos no pueden tener
dynamictipo. - No se admite para matrices ni tipos de intervalo (
Span<T>,ReadOnlySpan<T>). - Los argumentos de
with(...)no afectan a si existe una conversión de la expresión de colección a un tipo de destino candidato. La resolución de sobrecarga y la inferencia de tipos para una llamada envolvente omiten esos argumentos. Solo la presencia de unwith(...)elemento, no sus argumentos, afecta a si existe esa conversión. Una vez determinado el tipo de destino, los argumentos participan en la resolución de sobrecargas para su método constructor o generador de colecciones.
Por ejemplo, un argumento de comparador no indica al compilador que elija una HashSet<string> sobrecarga sobre una List<string> sobrecarga:
class Examples
{
static void Print(List<string> values) { }
static void Print(HashSet<string> values) { }
static void Example()
{
Print([with(StringComparer.OrdinalIgnoreCase), "one", "ONE"]);
}
}
La llamada produce el error CS0121: "La llamada es ambigua entre los métodos o propiedades siguientes". Aunque solo HashSet<string> tiene un constructor que acepta este comparador, el compilador omite los with(...) argumentos cuando elige la Print sobrecarga. Asigne primero la expresión de colección a una HashSet<string> variable para seleccionar esa sobrecarga.
Del mismo modo, with(...) los argumentos no proporcionan tipos de elemento para la inferencia de tipos genéricos:
class Examples
{
static void Use<T>(List<T> values) { }
static void Example()
{
Use([with(10)]);
}
}
Esta llamada produce el error CS0411: "Los argumentos de tipo no se pueden deducir del uso. Intente especificar explícitamente los argumentos de tipo". El argumento capacity no determina Ty la colección no tiene elementos de los que deducirlo. Especifique el argumento de tipo, como Use<int>([with(10)]).