Partilhar via


Argumentos de expressão de coleções

Edição campeã: https://github.com/dotnet/csharplang/issues/8887

Motivação

A funcionalidade de expressão do dicionário identificou a necessidade de expressões de coleção transmitirem dados especificados pelo utilizador, de modo a configurar o comportamento da coleção final. Especificamente, os dicionários permitem aos utilizadores personalizar a comparação das suas chaves, usando-as para definir igualdade entre chaves, e ordenação ou hash (no caso de coleções ordenadas ou hashadas, respetivamente). Esta necessidade aplica-se ao criar qualquer tipo de tipo de dicionário (como D d = new D(...), D d = D.CreateRange(...) e até IDictionary<...> d = <synthesized dict>)

Para apoiar isto, é proposto um novo with(...arguments...) elemento como o primeiro elemento de uma expressão de coleção, desta forma:

Dictionary<string, int> nameToAge = [with(comparer), .. d1, .. d2, .. d3];
  1. Ao traduzir para uma new CollectionType(...) chamada, estes ...arguments... são usados para determinar o construtor apropriado e são transmitidos em conformidade.
  2. Ao traduzir para uma CollectionFactory.Create chamada, estes ...arguments... são passados antes com o ReadOnlySpan<ElementType> argumento elementos, todos usados para determinar a sobrecarga apropriada Create , e são transmitidos em conformidade.
  3. Ao traduzir para uma interface (como IDictionary<,>), apenas um argumento é permitido. Implementa uma das conhecidas interfaces de comparadores BCL e será usado para controlar a semântica de comparação de chaves da instância final.

Esta sintaxe foi escolhida como:

  1. Mantém toda a informação dentro da [...] sintaxe. Garantir que o código ainda indica claramente que uma coleção está a ser criada.
  2. Não implica chamar um new construtor (quando não é assim que todas as coleções são criadas).
  3. Não implica criar/copiar os valores da coleção várias vezes (como um postfixo with { ... } poderia).
  4. Não distorce a ordem das operações, especialmente com a semântica consistente de avaliação de expressões da esquerda para a direita de C#. Por exemplo, não avalia os argumentos usados para construir uma coleção após avaliar as expressões usadas para preencher a coleção.
  5. Não obriga o utilizador a ler até ao fim de uma expressão de coleção (potencialmente grande) para determinar a semântica comportamental central. Por exemplo, ter de ver até ao fim de um dicionário de cem linhas, apenas para descobrir que, sim, estava a usar o comparador de chaves certo.
  6. Não é subtil, mas também não é excessivamente prolixo. Por exemplo, usar ; em vez de , para indicar argumentos é uma sintaxe muito fácil de ignorar. with() Só adiciona 6 caracteres e destaca-se facilmente, especialmente com a coloração sintática da with palavra-chave.
  7. Lê-se bem. "Esta é uma expressão de coleção 'com' estes argumentos, consistindo nestes elementos."
  8. Resolve a necessidade de comparadores tanto para dicionários como para conjuntos.
  9. Garante que qualquer necessidade do utilizador de passar argumentos, ou quaisquer necessidades que nós próprios tenhamos para além dos comparadores no futuro, já estejam tratadas.
  10. Não entra em conflito com nenhum código existente (uso https://grep.app/ para pesquisar).

Filosofia de Design

A secção abaixo aborda discussões anteriores sobre filosofia de design. Incluindo o motivo pelo qual certos formulários foram rejeitados.

Existem duas direções principais que podemos seguir para fornecer estes dados definidos pelo utilizador. A primeira é para valores apenas em casos especiais no espaço do comparador (que definimos como tipos herdados dos BCL's IComparer<T> ou IEqualityComparer<T> tipos). A segunda é fornecer um mecanismo generalizado para fornecer argumentos arbitrários à API final invocada ao criar expressões de coleção. A especificação principal da expressão do dicionário mostra como poderíamos fazer a primeira, enquanto esta especificação procura fazer a segunda.

Exames das soluções para comparadores que passaram revelaram fraquezas na sua abordagem caso quiséssemos expandi-las para argumentos arbitrários. Por exemplo:

  1. Reutilizando a sintaxe dos elementos , como fazemos com a forma: [StringComparer.OrdinalIgnoreCase, "mads": 21]. Isto funciona bem num espaço onde KeyValuePair<,> e os comparadores não herdam dos tipos comuns. Mas desmorona-se num mundo onde se poderia fazer: HashSet<object> h = [StringComparer.OrdinalIgnoreCase, "v"]. Isto está a transmitir um comparador? Ou tentar colocar dois valores de objeto no conjunto?

  2. Separar argumentos versus elementos com sintaxe subtil (como usar um ponto e vírgula em vez de uma vírgula para os separar em [comparer; v1]). Isto pode causar situações muito confusas em que um utilizador escreve [1; 2] acidentalmente (e recebe uma coleção que passa '1' como, por exemplo, o argumento 'capacidade' para um List<>, e contém apenas o valor único '2'), quando pretendia [1, 2] (uma coleção com dois elementos).

Por isso, para apoiar argumentos arbitrários, acreditamos que é necessária uma sintaxe mais óbvia para demarcar estes valores de forma mais clara. Várias outras preocupações de design também surgiram neste setor. Sem ordem específica, estes são:

  1. Que a solução não seja ambígua e cause falhas no código que as pessoas provavelmente usam com expressões de coleção hoje em dia. Por exemplo:

    List<Widget> c = [new(...), w1, w2, w3];
    

    Isto é legal hoje em dia, sendo a new(...) expressão 'criação implícita de objetos' que cria um novo widget. Não podemos reutilizar isto para transmitir argumentos ao List<>construtor de , pois certamente quebraria código existente.

  2. Que a sintaxe não se estenda para fora do [...] constructo. Por exemplo:

    HashSet<string> s = [...] with ...;
    

    Estas sintaxes podem ser interpretadas como significando que a coleção é criada primeiro e depois recriada numa forma diferente, implicando múltiplas transformações dos dados e custos potencialmente indesejados mais elevados (mesmo que isso não seja o que é emitido).

  3. Isso new , como uma palavra-chave potencial para usar neste espaço, é indesejável e confuso. Tanto porque [...] indica que um novo objeto foi criado, como porque as traduções da expressão da coleção podem passar por APIs não-construtoras (por exemplo, o padrão do método Create ).

  4. Que a solução não seja excessivamente prolixa. Uma proposta de valor central das expressões de coleção é a brevidade. Portanto, se o formulário adicionar uma grande quantidade de andaimes sintáticos, parecerá um passo atrás e minará a proposta de valor de usar expressões de coleção, em comparação com o recurso às APIs existentes para criar a coleção.

Note-se que uma sintaxe como new([...], ...) entra em conflito tanto com '2' como com '3' acima. Faz parecer que estamos a chamar para um construtor (quando pode não estar) e implica que uma expressão de coleção criada é passada para esse construtor, o que definitivamente não é.

Com base em tudo o que foi dito acima, surgiram algumas opções que se consideram resolver as necessidades dos argumentos passageiros, sem ultrapassar os objetivos das expressões de coleção.

[with(...arguments...)] Conceção

Syntax:

collection_element
   : expression_element
   | spread_element
+  | with_element
   ;

+with_element
+  : 'with' argument_list
+  ;

Há uma ambiguidade sintática imediatamente introduzida nesta produção gramatical. Semelhante à ambiguidade entre spread_element e expression_element ( explicada aqui), existe uma ambiguidade sintática imediata entre with_element e expression_element. Especificamente, with(<arguments>) é exatamente o corpo de produção para with_element, e também é acessível através de expression_element -> expression -> ... -> invocation_expression. Existe uma regra geral simples para collection_elements. Especificamente, se o elemento começa lexicamente com a sequência with( de tokens, então é sempre tratado como um with_element.

Isto é benéfico de duas formas. Primeiro, uma implementação de compilador só precisa de olhar para os tokens imediatamente seguintes que vê para determinar que tipo de elemento analisar. Em segundo lugar, correspondentemente, um utilizador pode compreender trivialmente que tipo de elemento tem sem ter de tentar mentalmente analisar o que se segue para ver se deve pensar nisso como um with_element ou um expression_element.

Exemplos

Exemplos de como isto ficaria são:

// With an existing type:

// Initialize to twice the capacity since we'll have to add
// more values later.
List<string> names = [with(capacity: values.Count * 2), .. values];

Estes formulários parecem "ler-se" razoavelmente bem. Em todos esses casos, o código está a "criar uma expressão de coleção, 'com' os seguintes argumentos para passar para controlar a instância final, e depois os elementos subsequentes usados para a preencher. Por exemplo, a primeira linha "cria uma lista de cadeias 'com' uma capacidade de duas vezes a contagem dos valores prestes a ser distribuídos nela"

Importa referir que este código tem pouca probabilidade de ser ignorado, como acontece com formas como: [arg; element], ao mesmo tempo que acrescenta verbosidade mínima, com grande flexibilidade para transmitir quaisquer argumentos desejados.

Isto seria tecnicamente uma alteração disruptiva, pois with(...)poderia ter sido uma chamada a um método pré-existente chamado with. No entanto, ao contrário new(...) do que é uma forma conhecida e recomendada de criar valores implícitamente tipados, with(...) é muito menos provável como nome de método, contrariando a nomenclatura .Net para métodos. No improvável caso de um utilizador ter tal método, certamente poderia continuar a aceder ao método existente usando @with(...).

Traduziríamos este with(...) elemento assim:

List<string> names = [with(/*capacity*/10), ...]; // translates to:

// argument_list *becomes* the argument list for the
// constructor call. 
__result = new List<string>(10); // followed by normal initialization

// or

IList<string> names2 = [with(capacity: 20), ...]; // translates to:

__result = new List<string>(20);

Por outras palavras, os argumentos argument_list seriam passados ao construtor apropriado se estivermos a chamar um construtor, ou ao 'método create' apropriado se estivermos a chamar tal método. Também permitiríamos que um único argumento herdado dos tipos de comparador BCL fosse fornecido ao instanciar um dos tipos de interface do dicionário de destino para controlar o seu comportamento.

Conversions

A secção de conversões para expressões de coleção é atualizada da seguinte forma:

> A struct or class type that implements System.Collections.IEnumerable where:

-  * The type has an applicable constructor that can be invoked with no arguments, and the constructor is accessible at the location of the collection expression.
+  a. the collection expression has no `with_element` and the type has an applicable constructor
+     that can be invoked with no arguments, accessible at the location of the collection expression. or
+  b. the collection expression has a `with_element` and the type has at least one constructor
+     accessible at the location of the collection expression. 

Note que os argumentos reais dentro do argument_listwith_element não afetam se a conversão existe ou não. Apenas a presença ou ausência do with_element próprio. A intuição aqui é simplesmente que, se a expressão de coleção for escrita sem um (como [x, y, z]), teria de ser para poder chamar o construtor sem args. Enquanto que, se for o há, [with(...), x, y, z] pode então chamar o construtor apropriado. Isto também significa que tipos que não podem ser invocados com um construtor sem argumento podem ser usados com uma expressão de coleção, mas apenas se essa expressão de coleção que contiver um with_element.

A determinação efetiva de como um with_element testamento afeta a construção é apresentada abaixo.

Construção

A construção é atualizada da seguinte forma.

Os elementos de uma expressão de coleção são avaliados por ordem, da esquerda para a direita. Dentro dos argumentos de coleção, os argumentos são avaliados por ordem, da esquerda para a direita. Cada elemento ou argumento é avaliado exatamente uma vez, e quaisquer referências adicionais referem-se aos resultados desta avaliação inicial.

Se collection_arguments for incluído e não for o primeiro elemento na expressão da coleção, é reportado um erro em tempo de compilação.

Se a lista de argumentos contiver valores com tipo dinâmico , é reportado um erro em tempo de compilação (LDM-2025-01-22).

Construtores

Se o tipo de destino for um tipo de struct ou de classe que implementa System.Collections.IEnumerable, e o tipo de destino não tiver um método create, e o tipo de destino não for um tipo de parâmetro genérico , então:

  • A resolução de sobrecarga é usada para determinar o melhor construtor de instâncias a partir dos candidatos.
  • O conjunto de construtores candidatos é composto por todos os construtores de instância acessíveis declarados no tipo alvo que são aplicáveis relativamente à lista de argumentos conforme definido no membro da função aplicável.
  • Se for encontrado um construtor de melhor instância, o construtor é invocado com a lista de argumentos.
    • Se o construtor tiver um params parâmetro, a invocação pode estar em forma expandida.
  • Caso contrário, é reportado um erro de ligação.
// List<T> candidates:
//   List<T>()
//   List<T>(IEnumerable<T> collection)
//   List<T>(int capacity)
List<int> l;
l = [with(capacity: 3), 1, 2]; // new List<int>(capacity: 3)
l = [with([1, 2]), 3];         // new List<int>(IEnumerable<int> collection)
l = [with(default)];           // error: ambiguous constructor

Métodos CollectionBuilderAttribute

Se o tipo alvo for um tipo com um método create, então:

  • A resolução de sobrecarga é usada para determinar o melhor método de criação entre os candidatos.
  • Para cada método create para o tipo alvo, definimos um método de projeção com uma assinatura idêntica ao método create, mas sem o último parâmetro.
  • O conjunto de métodos de projeção candidatos são os métodos de projeção que são aplicáveis relativamente à lista de argumentos conforme definido no membro da função aplicável.
  • Se for encontrado um melhor método de projeção, o método create correspondente é invocado com a lista de argumentos anexada a ReadOnlySpan<T> a contendo os elementos.
  • Caso contrário, é reportado um erro de ligação.
[CollectionBuilder(typeof(MyBuilder), "Create")]
class MyCollection<T> { ... }

class MyBuilder
{
    public static MyCollection<T> Create<T>(ReadOnlySpan<T> elements);
    public static MyCollection<T> Create<T>(IEqualityComparer<T> comparer, ReadOnlySpan<T> elements);
}
MyCollection<string> c1 = [with(GetComparer()), "1", "2"];
// IEqualityComparer<string> _tmp1 = GetComparer();
// ReadOnlySpan<string> _tmp2 = ["1", "2"];
// c1 = MyBuilder.Create<string>(_tmp1, _tmp2);

MyCollection<string> c2 = [with(), "1", "2"];
// ReadOnlySpan<string> _tmp3 = ["1", "2"];
// c2 = MyBuilder.Create<string>(_tmp3);

CollectionBuilderAttribute: Criar métodos

Para uma expressão de coleção onde a definição do tipo-alvo tem um [CollectionBuilder] atributo, os métodos create são os seguintes, atualizados a partir de expressões de coleção: métodos create.

Um [CollectionBuilder(...)] atributo especifica o tipo construtor e o nome do método de um método a ser invocado para construir uma instância do tipo de coleção.

O tipo construtor deve ser um não genérico class ou struct.

Primeiro, o conjunto de métodos de criação de aplicáveisCM é determinado. Consiste em métodos que cumprem os seguintes requisitos:

  • O método deve ter o nome especificado no atributo [CollectionBuilder(...)].
  • Deve-se definir o método diretamente no tipo de builder .
  • O método deve ser static.
  • O método deve estar acessível onde a expressão de coleção é usada.
  • A aridade do método deve corresponder à aridade do tipo de coleção.
  • O método deve ter um último parâmetro do tipo System.ReadOnlySpan<E>, passado por valor.
  • Há uma conversão de identidade , conversão de referência implícitaou conversão de encaixotamento do tipo de retorno do método para o tipo de coleção .

Os métodos declarados em tipos de base ou interfaces são ignorados e não fazem parte do conjunto de CM.

Para uma expressão de coleção com um tipo de destino C<S0, S1, …> onde a declaração de tipo associada C<T0, T1, …> possui um método construtor associado B.M<U0, U1, …>(), os argumentos genéricos de tipo do tipo de destino são aplicados em ordem, do tipo mais externo para o mais interno, ao método construtor .

As principais diferenças em relação ao algoritmo anterior são:

  • Os métodos Create podem ter parâmetros adicionais antes do ReadOnlySpan<E> parâmetro.
  • São suportados múltiplos métodos de criação.

Tipo de alvo de interface

Se o tipo alvo for um tipo de interface, então:

  • A resolução de sobrecarga é usada para determinar a melhor assinatura de método candidata.

  • O conjunto de assinaturas candidatas são as assinaturas abaixo para a interface alvo que são aplicáveis relativamente à lista de argumentos conforme definido no membro da função aplicável.

    Interfaces Assinaturas dos candidatos
    IEnumerable<E>
    IReadOnlyCollection<E>
    IReadOnlyList<E>
    () (sem parâmetros)
    ICollection<E>
    IList<E>
    List<E>()
    List<E>(int)

Se for encontrada uma assinatura de melhor método, a semântica é a seguinte:

  • A assinatura candidata para IEnumerable<E>, IReadOnlyCollection<E> e IReadOnlyList<E> é simplesmente () e tem o mesmo significado que não ter o with() elemento de todo.
  • As assinaturas candidatas para IList<T> e ICollection<T> são as assinaturas de List<T>() e List<T>(int) construtores. Ao construir o valor (ver Tradução de Interface Mutável), o respetivo List<T> construtor será invocado.
  • Caso contrário, é reportado um erro de ligação.

Dictionary-Interface tipo de alvo

Isto é especificado aqui como parte da característica definida em https://github.com/dotnet/csharplang/blob/main/proposals/dictionary-expressions.md.

A lista acima foi aumentada para conter os seguintes itens:

Interfaces Assinaturas dos candidatos
IReadOnlyDictionary<K, V> () (sem parâmetros)
(IEqualityComparer<K>? comparer)
IDictionary<K, V> Dictionary<K, V>()
Dictionary<K, V>(int)
Dictionary<K, V>(IEqualityComparer<K>)
Dictionary<K, V>(int, IEqualityComparer<K>)

Se for encontrada uma assinatura de melhor método, a semântica é a seguinte:

  • As assinaturas candidatas para IReadOnlyDictionary<K, V> são () (o que tem o mesmo significado que não ter o with() elemento de todo), e (IEqualityComparer<K>). Este comparador será usado para fazer o hash e comparar adequadamente as chaves no dicionário de destino que o compilador escolher criar (ver Tradução de Interfaces Não Mutáveis).
  • As assinaturas candidatas para IDictionary<T> são as assinaturas de Dictionary<K, V>(), Dictionary<K, V>(int), Dictionary<K, V>(IEqualityComparer<K>) e Dictionary<K, V>(int, IEqualityComparer<K>)construtores. Ao construir o valor (ver Tradução de Interface Mutável), o respetivo Dictionary<K, V> construtor será invocado.
  • Caso contrário, é reportado um erro de ligação.
IDictionary<string, int> d;
IReadOnlyDictionary<string, int> r;

d = [with(StringComparer.Ordinal)]; // new Dictionary<string, int>(StringComparer.Ordinal)
r = [with(StringComparer.Ordinal)]; // new $PrivateImpl<string, int>(StringComparer.Ordinal)

d = [with(capacity: 2)]; // new Dictionary<string, int>(capacity: 2)
r = [with(capacity: 2)]; // error: 'capacity' parameter not recognized
d = [with()];            // Legal: empty arguments supported for interfaces

Outros tipos de alvos

Se o tipo alvo for qualquer outro tipo, então é reportado um erro de ligação para a lista de argumentos, mesmo que vazia.

Span<int> a = [with(), 1, 2, 3]; // error: arguments not supported
Span<int> b = [with([1, 2]), 3]; // error: arguments not supported

int[] a = [with(), 1, 2, 3]; // error: arguments not supported
int[] b = [with(length: 1), 3]; // error: arguments not supported

Ref. segurança

Ajustamos as regras collection-expressions.md#ref-safety para ter em conta o with() elemento.

Ver também §16.4.15 Restrição de contexto seguro.

Criar métodos

Esta secção aplica-se a expressões de coleção cujo tipo-alvo cumpre as restrições definidas nos métodos CollectionBuilderAttribute.

O contexto seguro é determinado modificando uma cláusula de collection-expressions.md#ref-safety (alterações a negrito):

  • Se o tipo alvo for um tipo ref struct com um método create, o contexto seguro da expressão de coleção é o contexto seguro de uma invocação do método create, onde os argumentos são os with() argumentos do elemento seguidos pela expressão de coleção como argumento para o último parâmetro (o ReadOnlySpan<E> parâmetro).

Os argumentos do método devem corresponder a restrição aplica-se à expressão de coleção. De forma semelhante à determinação do contexto seguro acima, os argumentos do método devem corresponder a restrição é aplicada tratando a expressão de coleção como uma invocação do método create, onde os argumentos são os with() argumentos do elemento seguidos pela expressão de coleção como argumento para o último parâmetro.

Chamadas de construtor

Esta secção aplica-se a expressões de coleção cujo tipo-alvo cumpre as restrições definidas em Construtores.

Para uma expressão-coleção de um tipo de ref struct da seguinte forma:
[with(a₁, a₂, ..., aₙ), e₁, e₂, ..., eₙ]

O contexto seguro da expressão de coleção é o mais restrito dos contextos seguros das seguintes expressões:

  • Uma expressão new C(a₁, a₂, ..., aₙ)de criação de objetos , onde C é o tipo alvo
  • As expressões e₁, e₂, ..., eₙ dos elementos (ou as próprias expressões, ou o valor de espalhamento no caso de um elemento de dispersão).

Os argumentos do método devem corresponder a restrição aplica-se à expressão de coleção. A restrição é aplicada tratando a expressão de coleção como a criação de objetos da forma new C(a₁, a₂, ..., aₙ) { e₁, e₂, ..., eₙ } segundo low-level-struct-improvements.md#rules-for-object-initializers.

  • Os elementos de expressão são tratados como se fossem inicializadores de elementos de coleção.
  • Os elementos de spread são tratados de forma semelhante, assumindo temporariamente que C tem um Add(SpreadType spread) método, onde SpreadType é o tipo do valor do spread.

Perguntas respondidas

dynamic Argumentos

Devem ser permitidas discussões com dynamic o tipo? Isso pode exigir a utilização do binder de runtime para resolução de sobrecarga, o que dificultaria limitar o conjunto de candidatos, por exemplo para casos de construtor de coleções.

Resolução: Proibido. LDM-2025-01-22

with() Mudança de Última Hora

O elemento proposto with() é uma mudança decisiva.

object x, y, z = ...;
object[] items = [with(x, y), z]; // C#13: ok; C#14: error args not supported for object[]

object with(object x, object y) { ... }

Confirme que a alteração de quebra é aceitável e se a alteração de quebra deve estar ligada à versão da língua.

Resolução: Mantém o comportamento anterior (sem alterações graves) ao compilar com uma versão anterior da linguagem. LDM-2025-03-17

Os argumentos devem afetar a conversão da expressão da coleção?

Os argumentos de coleção e os métodos aplicáveis devem afetar a convertibilidade da expressão de coleção?

Print([with(comparer: null), 1, 2, 3]); // ambiguous or Print<int>(HashSet<int>)?

static void Print<T>(List<T> list) { ... }
static void Print<T>(HashSet<T> set) { ... }

Se os argumentos afetarem a convertibilidade com base nos métodos aplicáveis, os argumentos provavelmente também deverão afetar a inferência de tipos.

Print([with(comparer: StringComparer.Ordinal)]); // Print<string>(HashSet<string>)?

Para referência, casos semelhantes com tipo new() alvo resultam em erros.

Print<int>(new(comparer: null));              // error: ambiguous
Print(new(comparer: StringComparer.Ordinal)); // error: type arguments cannot be inferred

Resolução: Os argumentos de coleção devem ser ignorados nas conversões e na inferência de tipos. LDM-2025-03-17

Ordem dos parâmetros do método construtor de coleções

Para métodos construtores de coleções , o parâmetro span deve estar antes ou depois de quaisquer parâmetros para argumentos de coleção?

Os elementos primeiro permitiriam que os argumentos fossem declarados como opcionais.

class MySetBuilder
{
    public static MySet<T> Create<T>(ReadOnlySpan<T> items, IEqualityComparer<T> comparer = null) { ... }
}

Os argumentos primeiro permitiriam que o intervalo fosse um params parâmetro, para suportar a chamada diretamente em forma expandida.

var s = MySetBuilder.Create(StringComparer.Ordinal, x, y, z);

class MySetBuilder
{
    public static MySet<T> Create<T>(IEqualityComparer<T> comparer, params ReadOnlySpan<T> items) { ... }
}

Resolução: O parâmetro span para elementos deve ser o último parâmetro. LDM-2025-03-12

Argumentos com versões linguísticas anteriores

É reportado um erro ao with() compilar com uma versão anterior da linguagem, ou atribui-se with a outro símbolo do escopo?

Resolução: Não há alteração de quebra dentro with de uma expressão de coleção ao compilar com versões anteriores da linguagem. LDM-2025-03-17

Tipos-alvo onde são necessários argumentos

Devem ser suportadas conversões de expressões de coleção para tipos-alvo onde os argumentos devem ser fornecidos porque todos os métodos construtores ou de fábrica requerem pelo menos um argumento?

Tais tipos podiam ser usados com expressões de coleção que incluíam argumentos explícitos with() , mas os tipos não podiam ser usados para params parâmetros.

Por exemplo, considere o seguinte tipo construído a partir de um método de fábrica:

MyCollection<object> c;
c = [];                  // error: no arguments
c = [with(capacity: 1)]; // ok

[CollectionBuilder(typeof(MyBuilder), "Create")]
class MyCollection<T> : IEnumerable<T> { ... }

class MyBuilder
{
    public static MyCollection<T> Create<T>(ReadOnlySpan<T> items, int capacity) { ... }
}

A mesma questão aplica-se quando o construtor é chamado diretamente, como no exemplo abaixo.

No entanto, para os tipos-alvo onde o construtor é chamado diretamente, a conversão de expressões de coleção requer atualmente um construtor chamável sem argumentos, mas os argumentos de coleção são ignorados ao determinar a convertibilidade.

c = [];                  // error: no arguments
c = [with(capacity: 1)]; // error: no constructor callable with no arguments?

class MyCollection<T> : IEnumerable<T>
{
    public MyCollection(int capacity) { ... }
    public void Add(T t) { ... }
    // ...
}

Resolução: Suporta conversões para tipos-alvo onde todos os construtores ou métodos de fábrica requerem argumentos e exigem with() a conversão. LDM-2025-03-05

__arglist

Deveria __arglist ser suportado em with() Elements?

class MyCollection : IEnumerable
{
    public MyCollection(__arglist) { ... }
    public void Add(object o) { }
}

MyCollection c;
c = [with(__arglist())];    // ok
c = [with(__arglist(x, y)]; // ok

Resolução: Não há suporte para __arglist argumentos em cobrança a menos que seja gratuito. LDM-2025-03-05

Argumentos para tipos de interface

Devem ser suportados argumentos para tipos de alvo de interface?

ICollection<int> c = [with(capacity: 4)];
IReadOnlyDictionary<string, int> d = [with(comparer: StringComparer.Ordinal), ..values];
Se sim, que assinaturas de método são usadas ao ligar os argumentos?

Para tipos de interface mutáveis , as opções são:

  1. Use os construtores acessíveis do tipo bem conhecido necessário para a instância: List<T> ou Dictionary<K, V>.
  2. Use assinaturas independentes de um tipo específico, por exemplo usando new() e new(int capacity) para ICollection<T> e IList<T> (ver Construção para potenciais assinaturas para cada interface).

Usar os construtores acessíveis de um tipo bem conhecido tem as seguintes implicações:

  • Os nomes dos parâmetros, opcionalidade, params, são retirados diretamente dos parâmetros.
  • Todos os construtores acessíveis estão incluídos, mesmo que isso possa não ser útil para expressões de coleção, como List(IEnumerable<T>) que permitiria IList<int> list = [with(1, 2, 3)];.
  • O conjunto de construtores pode depender da versão BCL.

Recomendação: Use os construtores disponíveis dos tipos mais conhecidos. Garantimos que usaríamos estes tipos, por isso isto simplesmente 'cai' e é o caminho mais claro e simples para construir estes valores.

Para tipos de interface não mutáveis , as opções são semelhantes:

  1. Não faça nada. Isto
  2. Use assinaturas independentes do tipo específico, embora o único cenário possa ser new(IEqualityComparer<K> comparer) para IReadOnlyDictionary<K, V> C#14..

Usar construtores acessíveis de algum tipo bem conhecido (a estratégia para tipos de interface mutável) não é viável, pois não há relação com nenhum tipo particular existente, e o tipo final podemos usar e/ou sintetizar. Assim, teria de existir requisitos novos e estranhos para que o compilador pudesse mapear qualquer construtor existente desse tipo (mesmo à medida que evolui) para a instância não mutável que realmente gera.

Recomendação: Use assinaturas independentes de um tipo específico. E, para C# 14, apenas suporte new(IEqualityComparer<K> comparer) , IReadOnlyDictionary<K, V> pois essa é a única interface não mutável onde consideramos crítico para a usabilidade/semântica permitir que os utilizadores forneçam isto. Futuros lançamentos em C# podem considerar expandir este conjunto com base em justificações sólidas fornecidas.

Resolução:https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-04-23.md

São suportados argumentos para tipos de alvo de interface. Para interfaces mutáveis e não mutáveis, o conjunto de argumentos será selecionado.

A lista esperada (que ainda precisa de ser ratificada por LDM) é do tipo alvo de interface

Listas de argumentos vazias

Devemos permitir listas de argumentos vazias para alguns ou todos os tipos de alvo?

Um vazio with() seria equivalente a nenhum with(). Pode proporcionar alguma consistência com caixas não vazias, mas não acrescentaria nenhuma nova funcionalidade.

O significado de um 'with()' vazio pode ser mais claro para alguns tipos-alvo do que para outros: - Para tipos onde são usados **construtores**, chame-se o construtor aplicável sem argumentos. - Para tipos com **'CollectionBuilderAttribute'**, chame o método de fábrica aplicável apenas com elementos. - Para **tipos de interface**, constrói-se o tipo bem conhecido ou definido pela implementação sem argumentos. - No entanto, para **arrays** e **spans**, onde argumentos de coleção não são suportados de outra forma, 'with()' pode ser confuso.
List<int>           l = [with()]; // ok? new List<int>()
ImmutableArray<int> m = [with()]; // ok? ImmutableArray.Create<int>()

IList<int>       i = [with()]; // ok? new List<int>() or equivalent
IEnumerable<int> e = [with()]; // ok?

int[]     a = [with()]; // ok?
Span<int> s = [with()]; // ok?

Resolução:https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-05-12.md#empty-argument-lists

Vamos permitir with() para tipos de construtor e tipos de construtor que podem ser chamados sem argumentos, e vamos adicionar assinaturas de construtor vazias para os tipos de interface (mutáveis e apenas leitura). Arrays e spans não permitem com(), pois não existem assinaturas que se ajustem a eles.

Perguntas abertas

Finalização de uma preocupação em aberto de https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-03-17.md#conclusion

with(...) é uma alteração disruptiva na linguagem com [with(...)]. Antes desta característica, significa uma expressão de coleção com um elemento, que é o resultado de chamar a withexpressão de invocação. Após esta característica, torna-se uma coleção que recebe argumentos.

Queremos que esta pausa aconteça apenas quando um utilizador escolhe uma versão específica da língua (como C#-14/15?). Ou seja, se estiverem numa versão de linguagem mais antiga, obtêm a lógica de análise prévia, mas na versão mais recente recebem a lógica de análise mais recente. Ou queremos sempre que tenha a lógica de análise mais recente, mesmo numa versão de linguagem mais antiga?

Temos técnica prévia para ambas as estratégias. required, por exemplo, é sempre analisado com a nova lógica, independentemente da versão de língua. Enquanto isso, record/field e outros alteram a sua lógica de análise consoante a versão da língua.

Finalmente, isto tem sobreposição e impacto com Dictionary Expressions, que introduz a key:value sintaxe para os elementos KVP. Queremos estabelecer o comportamento que queremos para qualquer versão de língua, e para [with(...)] por si só, e coisas como [with(...) : expr] ou [expr : with(...)].