Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Problema do especialista: https://github.com/dotnet/csharplang/issues/8887
Motivação
O recurso de expressão de dicionário identificou a necessidade de expressões de coleção passarem dados especificados pelo usuário para configurar o comportamento da coleção final. Especificamente, os dicionários permitem que os usuários personalizem como suas chaves se comparam, usando-as para definir a igualdade entre chaves e classificação ou hash (no caso de coleções classificadas ou hash, respectivamente). Essa necessidade se aplica ao criar qualquer tipo de dicionário (como D d = new D(...), D d = D.CreateRange(...) e até mesmo IDictionary<...> d = <synthesized dict>)
Para dar suporte a isso, um novo with(...arguments...) elemento é proposto como o primeiro elemento de uma expressão de coleção como esta:
Dictionary<string, int> nameToAge = [with(comparer), .. d1, .. d2, .. d3];
- Ao traduzir para uma
new CollectionType(...)chamada, elas...arguments...são usadas para determinar o construtor apropriado e são transmitidas adequadamente. - Ao traduzir para uma
CollectionFactory.Createchamada, elas...arguments...são passadas antes com oReadOnlySpan<ElementType>argumento de elementos, todas usadas para determinar a sobrecarga apropriadaCreatee são transmitidas adequadamente. - Ao traduzir para uma interface (como
IDictionary<,>) apenas um único argumento é permitido. Ele implementa uma das interfaces do comparador BCL conhecidas e será usado para controlar a chave comparando a semântica da instância final.
Essa sintaxe foi escolhida:
- Mantém todas as informações dentro da
[...]sintaxe. Garantir que o código ainda indique claramente uma coleção que está sendo criada. - Não implica chamar um
newconstrutor (quando não é assim que todas as coleções são criadas). - Não implica criar/copiar os valores da coleção várias vezes (como um postfix
with { ... }pode. - Não contortica a ordem das operações, especialmente com a avaliação consistente da expressão da esquerda para a direita do C#ordenando semântica. Por exemplo, ele não avalia os argumentos usados para construir uma coleção depois de avaliar as expressões usadas para preencher a coleção.
- Não força um usuário a ler até o final de uma expressão de coleção (potencialmente grande) para determinar a semântica comportamental principal. Por exemplo, ter que ver até o final de um dicionário de cem linhas, apenas para descobrir que, sim, ele estava usando o comparador de chave correto.
- Ambos não são sutis, embora também não sejam excessivamente detalhados. Por exemplo, usar
;em vez de,indicar argumentos é uma parte muito fácil de perder.with()adiciona apenas seis caracteres e se destacará facilmente, especialmente com a coloração de sintaxe dawithpalavra-chave. - Lê bem. "Essa é uma expressão de coleção 'with' desses argumentos, que consiste nesses elementos."
- Resolve a necessidade de comparadores para dicionários e conjuntos.
- Garante que qualquer necessidade do usuário para passar argumentos ou quaisquer necessidades que nós mesmos tenhamos além dos comparadores no futuro já sejam tratadas.
- Não entra em conflito com nenhum código existente (usando https://grep.app/ para pesquisar).
Filosofia de Design
A seção abaixo aborda discussões anteriores sobre filosofia de design. Incluindo por que certos formulários foram rejeitados.
Há duas direções principais em que podemos entrar para fornecer esses dados definidos pelo usuário. A primeira é diferenciar apenas valores de maiúsculas e minúsculas no espaço do comparador (que definimos como tipos herdando dos tipos ou IEqualityComparer<T> bclIComparer<T>). A segunda é fornecer um mecanismo generalizado para fornecer argumentos arbitrários à API invocada final ao criar expressões de coleção. A especificação de expressão de dicionário primário mostra como podemos fazer o primeiro, enquanto essa especificação busca fazer o último.
Exames das soluções para apenas passar comparadores revelaram fraquezas em sua abordagem se quiséssemos expandi-las para argumentos arbitrários. Por exemplo:
Reutilizando a sintaxe do elemento , como fazemos com o formulário:
[StringComparer.OrdinalIgnoreCase, "mads": 21]. Isso funciona bem em um espaço emKeyValuePair<,>que os comparadores não herdam de tipos comuns. Mas ele quebra em um mundo onde se pode fazer:HashSet<object> h = [StringComparer.OrdinalIgnoreCase, "v"]. Isso está passando um comparador? Ou tentando colocar dois valores de objeto no conjunto?Separando argumentos versus elementos com sintaxe sutil (como usar um ponto e vírgula em vez de uma vírgula para separá-los).
[comparer; v1]Isso corre o risco de situações muito confusas em que um usuário grava[1; 2]acidentalmente (e obtém uma coleção que passa '1' como, digamos, o argumento 'capacity' para umList<>, e contém apenas o valor único '2'), quando ele pretendia[1, 2](uma coleção com dois elementos).
Por isso, para dar suporte a argumentos arbitrários, acreditamos que uma sintaxe mais óbvia é necessária para demarcar mais claramente esses valores. Várias outras preocupações de design também surgiram neste espaço. Em nenhuma ordem específica, estes são:
Que a solução não seja ambígua e cause quebras com o código que as pessoas provavelmente estão usando com expressões de coleção hoje em dia. Por exemplo:
List<Widget> c = [new(...), w1, w2, w3];Isso é legal hoje, com a
new(...)expressão sendo uma "criação de objeto implícito" que cria um novo widget. Não é possível redefinir isso para passar argumentos para oList<>construtor, pois isso certamente quebraria o código existente.Que a sintaxe não se estenda para fora da construção
[...]. Por exemplo:HashSet<string> s = [...] with ...;Essas sintaxes podem ser interpretadas para significar que a coleção é criada primeiro e, em seguida, recriada em uma forma diferente, implicando várias transformações dos dados e custos mais altos potencialmente indesejados (mesmo que não seja isso que é emitido).
Isso
newcomo uma palavra-chave potencial a ser usada neste espaço é indesejávelmente confuso. Tanto porque[...]já indica que um novo objeto foi criado e porque as traduções da expressão de coleção podem passar por APIs não construtoras (por exemplo, o padrão do método Create ).Que a solução não seja excessivamente detalhada. Uma proposta de valor principal de expressões de coleção é a brevidade. Portanto, se o formulário adicionar uma grande quantidade de scaffolding sintactic, ele se sentirá como um passo para trás e reduzirá a proposta de valor do uso de expressões de coleção, em vez de chamar as APIs existentes para fazer a coleção.
Observe que uma sintaxe como new([...], ...) execuções abaixo de '2' e '3' acima. Isso faz parecer que estamos chamando um construtor (quando talvez não estejamos) e isso implica que uma expressão de coleção criada é passada para esse construtor, o que definitivamente não é.
Com base em todas as opções acima, surgiram poucas opções que são sentidas para resolver as necessidades de passar argumentos, sem sair dos limites dos objetivos das expressões de coleção.
[with(...arguments...)] Design
Syntax:
collection_element
: expression_element
| spread_element
+ | with_element
;
+with_element
+ : 'with' argument_list
+ ;
Há uma ambiguidade sintactica imediatamente introduzida com essa produção gramatical. Semelhante à ambiguidade entre spread_element e expression_element (explicado aqui, há uma ambiguidade sintactica 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 expression_element -> expression -> ... -> invocation_expression. Há uma regra abrangente simples para collection_elements. Especificamente, se o elemento começar lexicamente com a sequência with( de token, ele sempre será tratado como um with_element.
Isso é benéfico de duas maneiras. Primeiro, uma implementação de compilador só precisa examinar os tokens imediatamente a seguir que ele vê para determinar que tipo de elemento analisar. Em segundo lugar, correspondentemente, um usuário pode entender trivialmente que tipo de elemento ele tem sem ter que tentar mentalmente analisar o que se segue para ver se ele deve pensar nele como um with_element ou um expression_element.
Exemplos
Exemplos de como isso seria:
// 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];
Esses formulários parecem "ler" razoavelmente bem. Em todos esses casos, o código está "criando uma expressão de coleção, 'com' os argumentos a seguir para passar para controlar a instância final e, em seguida, os elementos subsequentes usados para preenchê-la. Por exemplo, a primeira linha "cria uma lista de cadeias de caracteres 'com' uma capacidade de duas vezes a contagem dos valores prestes a serem distribuídos nela"
É importante ressaltar que esse código tem poucas chances de ser ignorado como em formulários como: [arg; element], ao mesmo tempo em que adiciona um mínimo de verbosidade, com uma grande flexibilidade para passar os argumentos desejados.
Tecnicamente, isso seria uma alteração significativa, como with(...)poderia ter sido uma chamada para um método pré-existente chamado with. No entanto, ao contrário new(...) do que é uma maneira conhecida e recomendada de criar valores de tipo implícito, with(...) é muito menos provável como um nome de método, executando a falta de nomenclatura .Net para métodos. No caso improvável de um usuário ter esse método, ele certamente seria capaz de continuar chamando o método existente usando @with(...).
Traduziríamos esse with(...) elemento da seguinte maneira:
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);
Em outras palavras, os argumentos argument_list seriam passados para o construtor apropriado se estivermos chamando um construtor ou para o "método create" apropriado se estivermos chamando esse método. Também permitiríamos que um único argumento herdando dos tipos de comparador BCL fosse fornecido ao criar uma instância de um dos tipos de interface de dicionário de destino para controlar seu comportamento.
Conversions
A seção conversões para expressões de coleção é atualizada da seguinte maneira:
> 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.
Observe que os argumentos reais dentro argument_list do do with_element não afetam se a conversão existir ou não. Apenas a presença ou ausência with_element de si mesmo. A intuição aqui é simplesmente que, se a expressão da coleção for escrita sem uma (como [x, y, z]) teria que ser capaz de chamar o construtor sem args. Embora se ele tiver [with(...), x, y, z] , ele poderá chamar o construtor apropriado. Isso 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 somente se essa expressão de coleção que contém um with_element.
A determinação real de como um with_element afetará a construção é fornecida abaixo.
Construção
A construção é atualizada da seguinte maneira.
Os elementos de uma expressão de coleção são avaliados em ordem, da esquerda para a direita. Dentro dos argumentos de coleção, os argumentos são avaliados em ordem, da esquerda para a direita. Cada elemento ou argumento é avaliado exatamente uma vez e as referências adicionais referem-se aos resultados dessa avaliação inicial.
Se collection_arguments for incluído e não for o primeiro elemento na expressão de coleção, um erro de tempo de compilação será relatado.
Se a lista de argumentos contiver valores com tipo dinâmico , um erro de tempo de compilação será relatado (LDM-2025-01-22).
Construtores
Se o tipo de destino for um struct ou tipo de classe que implementa System.Collections.IEnumerablee 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ância dos candidatos.
- O conjunto de construtores candidatos é todos construtores de instância acessíveis declarados no tipo de destino aplicável em relação à lista de argumentos , conforme definido no membro da função aplicável.
- Se um construtor de melhor instância for encontrado, o construtor será invocado com a lista de argumentos.
- Se o construtor tiver um
paramsparâmetro, a invocação poderá estar em forma expandida.
- Se o construtor tiver um
- Caso contrário, um erro de associação será relatado.
// 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 de destino 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 dos candidatos.
- Para cada método create para o tipo de destino, 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 de candidato são os métodos de projeção aplicáveis em relação à lista de argumentos , conforme definido no membro da função aplicável.
- Se um método de melhor projeção for encontrado, o método create correspondente será invocado com a lista de argumentos acrescentada com um
ReadOnlySpan<T>que contém os elementos. - Caso contrário, um erro de associação será relatado.
[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 em que a definição de tipo de destino tem um [CollectionBuilder] atributo, os métodos create são os seguintes , atualizados a partir de expressões de coleção: criar métodos.
Um
[CollectionBuilder(...)]atributo especifica o tipo de 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 de construtor deve ser um
classnão genérico ou umstructnão genérico.Primeiro, o conjunto de métodos de criação
CMaplicáveis é determinado. Consiste em métodos que atendem aos seguintes requisitos:
- O método deve ter o nome especificado no atributo
[CollectionBuilder(...)].- O método deve ser definido diretamente no tipo de construtor.
- O método deve ser
static.- O método deve ser 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 de tipo
System.ReadOnlySpan<E>, passado por valor.- Há uma conversão de identidade, uma conversão de referência implícita , ou uma conversão boxing do tipo de retorno do método para o tipo de coleção.
Métodos declarados em tipos base ou interfaces são ignorados e não fazem parte do conjunto
CM.
Para uma expressão de coleção com um tipo de destino
C<S0, S1, …>em que a declaração de tipoC<T0, T1, …>tem um método do construtorB.M<U0, U1, …>()associado, os argumentos de tipo genérico do tipo de destino são aplicados em ordem — e do tipo de conteúdo mais externo para o mais interno — ao método do construtor.
As principais diferenças do algoritmo anterior são:
- Criar métodos pode ter parâmetros adicionais antes do
ReadOnlySpan<E>parâmetro. - Há suporte para vários métodos de criação.
Tipo de destino da interface
Se o tipo de destino for um tipo de interface, então:
A resolução de sobrecarga é usada para determinar a melhor assinatura do método candidate.
O conjunto de assinaturas candidatas são as assinaturas abaixo para a interface de destino aplicável em relação à lista de argumentos , conforme definido no membro da função aplicável.
Interfaces Assinaturas de candidato IEnumerable<E>IReadOnlyCollection<E>IReadOnlyList<E>()(sem parâmetros)ICollection<E>IList<E>List<E>()List<E>(int)
Se uma melhor assinatura de método for encontrada, a semântica será a seguinte:
- A assinatura candidata para
IEnumerable<E>,IReadOnlyCollection<E>eIReadOnlyList<E>é simplesmente()e tem o mesmo significado que não ter owith()elemento em tudo. - As assinaturas candidatas para
IList<T>eICollection<T>são as assinaturas eList<T>(int)List<T>()construtores. Ao construir o valor (consulte Conversão de Interface Mutável), o respectivoList<T>construtor será invocado. - Caso contrário, um erro de associação será relatado.
Dictionary-Interface tipo de destino
Isso é especificado aqui como parte do recurso definido em https://github.com/dotnet/csharplang/blob/main/proposals/dictionary-expressions.md.
A lista acima é aumentada para ter os seguintes itens:
| Interfaces | Assinaturas de candidato |
|---|---|
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 uma melhor assinatura de método for encontrada, a semântica será a seguinte:
- As assinaturas candidatas
IReadOnlyDictionary<K, V>são()(que tem o mesmo significado de não ter owith()elemento) e(IEqualityComparer<K>). Esse comparador será usado para fazer hash adequadamente e comparar as chaves no dicionário de destino que o compilador escolhe criar (consulte Conversão de Interface Não Mutável). - As assinaturas candidatas
IDictionary<T>são as assinaturas deDictionary<K, V>()construtoresDictionary<K, V>(IEqualityComparer<K>)Dictionary<K, V>(int)eDictionary<K, V>(int, IEqualityComparer<K>)construtores. Ao construir o valor (consulte Conversão de Interface Mutável), o respectivoDictionary<K, V>construtor será invocado. - Caso contrário, um erro de associação será relatado.
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 destino
Se o tipo de destino for qualquer outro tipo, um erro de associação será relatado para a lista de argumentos, mesmo que vazio.
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
Segurança de ref
Ajustamos as regras collection-expressions.md#ref-safety para considerar o with() elemento.
Consulte também a restrição de contexto seguro §16.4.15.
Métodos de criação
Esta seção se aplica a expressões de coleção cujo tipo de destino atende às restrições definidas nos métodos CollectionBuilderAttribute.
O contexto seguro é determinado modificando uma cláusula de collection-expressions.md#ref-safety (alterações em negrito):
- Se o tipo de destino for um tipo de struct ref com um método create, o contexto seguro da expressão de coleção será o contexto seguro de uma invocação do método create em que os argumentos são os argumentos de
with()elemento seguidos pela expressão de coleção como o argumento para o último parâmetro (oReadOnlySpan<E>parâmetro).
Os argumentos do método devem corresponder à restrição aplicada à expressão de coleção. Da mesma forma que a determinação de contexto seguro acima, os argumentos do método devem corresponder à restrição é aplicado tratando a expressão de coleção como uma invocação do método create, em que os argumentos são os argumentos de with() elemento seguidos pela expressão de coleção como o argumento para o último parâmetro.
Chamadas de construtor
Esta seção se aplica a expressões de coleção cujo tipo de destino atende às restrições definidas em Construtores.
Para uma expressão de coleção de um tipo de struct ref do seguinte formulário:
[with(a₁, a₂, ..., aₙ), e₁, e₂, ..., eₙ]
O contexto seguro da expressão de coleção é o mais estreito dos contextos seguros das seguintes expressões:
- Uma expressão
new C(a₁, a₂, ..., aₙ)de criação de objeto, ondeCestá o tipo de destino - As expressões
e₁, e₂, ..., eₙde elemento (as próprias expressões ou o valor de propagação no caso de um elemento de propagação).
Os argumentos do método devem corresponder à restrição aplicada à expressão de coleção. A restrição é aplicada tratando a expressão de coleção como uma criação de objeto do formulário new C(a₁, a₂, ..., aₙ) { e₁, e₂, ..., eₙ } por low-level-struct-improvements.md#rules-for-object-initializers.
- Os elementos de expressão são tratados como se fossem inicializadores de elemento de coleção.
- Elementos de propagação são tratados da mesma forma, assumindo temporariamente que
Ctem umAdd(SpreadType spread)método, ondeSpreadTypeestá o tipo do valor de propagação.
Perguntas respondidas
Argumentos de dynamic
Os argumentos com dynamic tipo devem ser permitidos? Isso pode exigir o uso do associador de runtime para resolução de sobrecarga, o que dificultaria a limitação do conjunto de candidatos, por exemplo, para casos de construtor de coleção.
Resolução: Desaprovados. LDM-2025-01-22
with() alteração interruptiva
O elemento proposto with() é uma alteração significativa.
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 se a alteração interruptiva é aceitável e se a alteração significativa deve estar vinculada à versão do idioma.
Resolução: Mantenha o comportamento anterior (sem alteração significativa) ao compilar com a versão anterior do idioma. LDM-2025-03-17
Os argumentos devem afetar a conversão de expressão de coleção?
Os argumentos de coleção e os métodos aplicáveis devem afetar a conversibilidade 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 conversibilidade com base nos métodos aplicáveis, os argumentos provavelmente também devem afetar a inferência de tipo.
Print([with(comparer: StringComparer.Ordinal)]); // Print<string>(HashSet<string>)?
Para referência, casos semelhantes com tipo de destino resultam new() 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 em conversões e inferência de tipo. LDM-2025-03-17
Ordem de parâmetro do método do construtor de coleção
Para métodos de construtor de coleção , o parâmetro de intervalo deve ser antes ou depois de qualquer parâmetro 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 dar suporte à chamada diretamente na 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 de intervalo para elementos deve ser o último parâmetro. LDM-2025-03-12
Argumentos com versão de idioma anterior
Um erro é relatado ao with() compilar com uma versão de idioma anterior ou associa-se with a outro símbolo no escopo?
Resolução: Nenhuma alteração significativa para with dentro de uma expressão de coleção ao compilar com versões de idioma anteriores.
LDM-2025-03-17
Tipos de destino em que os argumentos são necessários
As conversões de expressão de coleção devem ter suporte em tipos de destino em que os argumentos devem ser fornecidos porque todos os construtores ou métodos de fábrica exigem pelo menos um argumento?
Esses tipos podem ser usados com expressões de coleção que incluem argumentos explícitos with() , mas os tipos não puderam 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 pergunta se aplica a quando o construtor é chamado diretamente como no exemplo abaixo.
No entanto, para os tipos de destino em que o construtor é chamado diretamente, a conversão de expressão de coleção atualmente requer um construtor que pode ser chamado sem argumentos, mas os argumentos da coleção são ignorados ao determinar a conversibilidade.
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: Suporte a conversões em tipos de destino em que todos os construtores ou métodos de fábrica exigem argumentos e exigem with() a conversão.
LDM-2025-03-05
__arglist
Deve __arglist ter suporte em with() elementos?
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 de coleção, a menos que seja gratuito.
LDM-2025-03-05
Argumentos para tipos de interface
Os argumentos devem ter suporte para tipos de destino de interface?
ICollection<int> c = [with(capacity: 4)];
IReadOnlyDictionary<string, int> d = [with(comparer: StringComparer.Ordinal), ..values];
Para tipos de interface mutáveis , as opções são:
- Use os construtores acessíveis do tipo conhecido necessário para a instanciação:
List<T>ouDictionary<K, V>. - Use assinaturas independentes de tipo específico, por exemplo, usando
new()enew(int capacity)paraICollection<T>eIList<T>(consulte Construção para possíveis assinaturas para cada interface).
Usar os construtores acessíveis de um tipo conhecido tem as seguintes implicações:
- Os nomes de parâmetro, opcional,
paramssão retirados diretamente dos parâmetros. - Todos os construtores acessíveis são incluídos, embora isso possa não ser útil para expressões de coleção, como
List(IEnumerable<T>)o que permitiriaIList<int> list = [with(1, 2, 3)];. - O conjunto de construtores pode depender da versão bcl.
Recomendação: use os construtores acessíveis dos tipos conhecidos. Garantimos que usaríamos esses tipos, portanto, esse é apenas "cai" e é o caminho mais claro e mais simples para construir esses valores.
Para tipos de interface não mutáveis , as opções são semelhantes:
- Não faça nenhuma ação. Este
- Use assinaturas independentes de tipo específico, embora o único cenário possa ser
new(IEqualityComparer<K> comparer)paraIReadOnlyDictionary<K, V>C#14..
O uso de construtores acessíveis de algum tipo conhecido (a estratégia para mutable-interface-types) não é viável, pois não há nenhuma relação com qualquer tipo existente específico e o tipo final que podemos usar e/ou sintetizar. Como tal, teria que haver novos requisitos ímpares que o compilador possa mapear qualquer construtor existente do tipo dito (mesmo que evolua) para a instância não mutável que ele realmente gera.
Recomendação: use assinaturas independentes de um tipo específico. E, para o C# 14, só há suporte new(IEqualityComparer<K> comparer) para IReadOnlyDictionary<K, V> essa é a única interface não mutável em que achamos que é essencial para a usabilidade/semântica permitir que os usuários forneçam isso. Versões futuras do C# podem considerar a expansão nesse conjunto com base em justificativas sólidas fornecidas.
Resolução:https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-04-23.md
Há suporte para argumentos para tipos de destino de interface. Para interfaces mutáveis e não mutáveis, o conjunto de argumentos será coletado.
A lista esperada (que ainda precisa ser ratificada pelo LDM) é o tipo de destino interface
Listas de argumentos vazias
Devemos permitir listas de argumentos vazias para alguns ou todos os tipos de destino?
Um vazio with() seria equivalente a nenhum with(). Ele pode fornecer alguma consistência com casos não vazios, mas não adicionaria nenhuma nova funcionalidade.
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
Permitiremos with() para tipos de construtor e tipos de construtor que podem ser chamados sem argumentos e adicionaremos assinaturas de construtor vazias para os tipos de interface (mutável e legível). Matrizes e intervalos não permitirão com(), pois não há assinaturas que se ajustem a elas.
Perguntas abertas
Finalizando uma preocupação aberta de https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-03-17.md#conclusion
with(...) é uma alteração significativa no idioma com [with(...)]. Antes desse recurso, isso significa uma expressão de coleção com um elemento, que é o resultado da chamada withde -invocação-expressão. Depois desse recurso, ele é uma coleção, que tem argumentos passados para ele.
Desejamos que essa interrupção ocorra somente quando um usuário escolher uma versão de idioma específica (como C#-14/15?). Em outras palavras, se eles estiverem em uma langversion mais antiga, eles obterão a lógica de análise anterior, mas na versão mais recente eles obterão a lógica de análise mais recente. Em Ou sempre queremos que ele tenha a lógica de análise mais recente, mesmo em uma langversion mais antiga?
Temos arte prévia para ambas as estratégias.
required, por exemplo, é sempre analisado com a nova lógica, independentemente da langversion. Enquanto, record/field e outras pessoas fabricam sua lógica de análise dependendo da versão da linguagem.
Por fim, isso tem sobreposição e impacto com Dictionary Expressions, o que apresenta a key:value sintaxe para elementos KVP. Queremos estabelecer o comportamento que queremos para qualquer versão lang, e por [with(...)] conta própria, e coisas como [with(...) : expr] ou [expr : with(...)].
C# feature specifications