Nota
O acesso a esta página requer autorização. Podes tentar iniciar sessão ou mudar de diretório.
O acesso a esta página requer autorização. Podes tentar mudar de diretório.
Observação
Este artigo é uma especificação de recurso. A especificação serve como o documento de design para o recurso. Ele inclui mudanças de especificação propostas, juntamente com as informações necessárias durante o design e desenvolvimento do recurso. Estes artigos são publicados até que as alterações de especificações propostas sejam finalizadas e incorporadas na especificação ECMA atual.
Pode haver algumas discrepâncias entre a especificação do recurso e a implementação concluída. Essas diferenças são capturadas nas notas pertinentes da Language Design Meeting (LDM).
Você pode saber mais sobre o processo de adoção de especificações de recursos no padrão de linguagem C# no artigo sobre as especificações .
Questão prioritária: https://github.com/dotnet/csharplang/issues/7700
Resumo
Na linguagem C# 12, foi adicionado suporte para a criação de instâncias de tipos de coleção além de apenas matrizes.
Veja as expressões da coleção .
A presente proposta estende o apoio params a todos esses tipos de coleção.
Motivação
Um parâmetro de matriz params fornece uma maneira conveniente de chamar um método que usa uma lista de comprimento arbitrário de argumentos.
Hoje o parâmetro params deve ser do tipo matriz. No entanto, pode ser benéfico para um desenvolvedor poder ter a mesma conveniência ao chamar APIs que usam outros tipos de coleção. Por exemplo, um ImmutableArray<T>, ReadOnlySpan<T>ou IEnumerablesimples. Especialmente nos casos em que o compilador é capaz de evitar uma alocação de matriz implícita com a finalidade de criar a coleção (ImmutableArray<T>, ReadOnlySpan<T>, etc).
Hoje, em situações em que uma API usa um tipo de coleção, os desenvolvedores geralmente adicionam uma sobrecarga de params que recebe uma matriz, constroem a coleção de destino e chamam a sobrecarga original com essa coleção, assim os consumidores da API têm que trocar uma alocação extra de matriz por conveniência.
Outra motivação é a capacidade de adicionar uma sobrecarga de parâmetros variáveis de extensões e garantir que ela tenha prioridade sobre a versão em formato de matriz, simplesmente recompilando o código-fonte existente.
Projeto detalhado
Parâmetros do método
Os parâmetros da secção do Método são ajustados da seguinte forma.
formal_parameter_list
: fixed_parameters
- | fixed_parameters ',' parameter_array
+ | fixed_parameters ',' parameter_collection
- | parameter_array
+ | parameter_collection
;
-parameter_array
+parameter_collection
- : attributes? 'params' array_type identifier
+ : attributes? 'params' 'scoped'? type identifier
;
Um parameter_collection consiste em um conjunto opcional de atributos , um modificador de params, um modificador de scoped opcional, um tipo de e um identificador de . Uma coleção de parâmetros declara um único parâmetro do tipo dado com o nome fornecido.
O tipo de uma coleção de parâmetros deve ser um dos seguintes tipos de destino válidos para uma expressão de coleção (ver https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#conversions):
- Um tipo de matriz unidimensional
T[], caso em que o tipo de elemento éT - Um intervalo do tipo
System.Span<T>System.ReadOnlySpan<T>
em que casos o tipo de elemento éT
- Um tipo com um método create apropriado que pode ser invocado sem argumentos adicionais, que é pelo menos tão acessível quanto o membro declarante, e com um tipo de elemento correspondente resultante dessa determinação
- Um struct ou tipo de classe que implementa
System.Collections.IEnumerableonde:O tipo tem um construtor que pode ser invocado sem argumentos, e o construtor é pelo menos tão acessível quanto o membro declarante.
O tipo tem um método de instância (não uma extensão)
Addonde:- O método pode ser invocado com um único argumento de valor.
- Se o método for genérico, os argumentos de tipo podem ser inferidos a partir do argumento.
- O método é pelo menos tão acessível quanto o membro declarante.
Nesse caso, o tipo de elemento é o tipo de iteração do tipo .
- Tipo de interface
-
System.Collections.Generic.IEnumerable<T>, -
System.Collections.Generic.IReadOnlyCollection<T>, -
System.Collections.Generic.IReadOnlyList<T>, -
System.Collections.Generic.ICollection<T>, System.Collections.Generic.IList<T>
em que casos o tipo de elemento éT
-
Em uma invocação de método, uma coleção de parâmetros permite que um único argumento do tipo de parâmetro dado seja especificado ou permite que zero ou mais argumentos do tipo de elemento da coleção sejam especificados. As coleções de parâmetros são descritas mais detalhadamente em Parameter collections.
Uma parameter_collection pode ocorrer após um parâmetro opcional, mas não pode ter um valor padrão – a omissão de argumentos para um parameter_collection resultaria, em vez disso, na criação de uma coleção vazia.
Coleções de parâmetros
A seção matrizes de parâmetros é renomeada e ajustada da seguinte forma.
Um parâmetro declarado com um modificador de params é uma coleção de parâmetros. Se uma lista formal de parâmetros incluir um parâmetro de coleção, este deve ser o último parâmetro da lista e deve ser do tipo especificado na secção parâmetros de método.
Nota: Não é possível combinar o modificador
paramscom os modificadoresin,outouref. nota final
Uma coleção de parâmetros permite que os argumentos sejam especificados de uma das duas maneiras em uma invocação de método:
- O argumento dado para uma coleção de parâmetros pode ser uma única expressão que é implicitamente conversível para o tipo de coleção de parâmetros. Neste caso, a coleção de parâmetros age precisamente como um parâmetro de valor.
- Como alternativa, a invocação pode especificar zero ou mais argumentos para a coleção de parâmetros, onde cada argumento é uma expressão que é implicitamente conversível para o tipo de elemento da coleção de parâmetros. Nesse caso, a invocação cria uma instância do tipo de coleção de parâmetros de acordo com as regras especificadas em expressões de coleção, como se os argumentos fossem utilizados como elementos de expressão em uma expressão de coleção na mesma ordem, e usa a instância de coleção recém-criada como o argumento real. Ao construir a instância de coleção, os argumentos originais não convertidos são usados.
Exceto por permitir um número variável de argumentos em uma invocação, uma coleção de parâmetros é precisamente equivalente a um parâmetro de valor do mesmo tipo.
Ao executar a resolução de sobrecarga, um método com uma coleção de parâmetros pode ser aplicável, em sua forma normal ou em sua forma expandida. A forma expandida de um método só está disponível se a forma normal do método não for aplicável e apenas se um método aplicável com a mesma assinatura que o formulário expandido não for já declarado no mesmo tipo.
Uma ambiguidade potencial surge entre a forma normal e a forma expandida do método com um único argumento de coleta de parâmetros quando ele pode ser usado como a própria coleção de parâmetros e como o elemento da coleção de parâmetros ao mesmo tempo. No entanto, a ambiguidade não apresenta problemas, uma vez que pode ser resolvida inserindo um elenco ou usando uma expressão de coleção, se necessário.
Assinaturas e sobrecarga
Todas as regras em torno do modificador params em Assinaturas e sobrecarga permanecem como estão.
Membro da função aplicável
A secção membro da função aplicável é ajustada da seguinte forma.
Se um membro de função que inclui uma coleção de parâmetros não for aplicável em sua forma normal, o membro da função poderá, em vez disso, ser aplicável em sua forma expandida:
- Se a coleção de parâmetros não for uma matriz, um formulário expandido não será aplicável às versões de idioma C# 12 e inferiores.
- O formulário expandido é construído substituindo a coleção de parâmetros na declaração de membro da função por zero ou mais parâmetros de valor do tipo de elemento da coleção de parâmetros de modo que o número de argumentos na lista de argumentos
Acorresponda ao número total de parâmetros. SeAtiver menos argumentos do que o número de parâmetros fixos na declaração do membro da função, a forma expandida do membro da função não pode ser construída e, portanto, não é aplicável. - Caso contrário, a forma expandida será aplicável se, para cada argumento em
A, uma das seguintes opções for verdadeira:- o modo de passagem de parâmetros do argumento é idêntico ao modo de passagem de parâmetros do parâmetro correspondente, e
- para um parâmetro de valor fixo ou um parâmetro de valor criado pela expansão, existe uma conversão implícita da expressão de argumento para o tipo do parâmetro correspondente, ou
- Para um parâmetro
in,outouref, o tipo da expressão de argumento é idêntico ao tipo do parâmetro correspondente.
- O modo de passagem de parâmetros do argumento é valor, e o modo de passagem de parâmetros do parâmetro correspondente é entrada, e existe uma conversão implícita da expressão do argumento para o tipo do parâmetro correspondente
- o modo de passagem de parâmetros do argumento é idêntico ao modo de passagem de parâmetros do parâmetro correspondente, e
Membro de função melhorado
A secção membro da função Better é ajustada da seguinte forma.
Dada uma lista de argumentos A com um conjunto de expressões de argumento {E₁, E₂, ..., Eᵥ} e dois membros de função aplicáveis Mᵥ e Mₓ com tipos de parâmetros {P₁, P₂, ..., Pᵥ} e {Q₁, Q₂, ..., Qᵥ}, Mᵥ é definido como um membro de função melhor do que Mₓ se
- para cada argumento, a conversão implícita de
EᵥparaQᵥnão é melhor do que a conversão implícita deEᵥparaPᵥ, e - Para pelo menos um argumento, a conversão de
EᵥparaPᵥé melhor do que a conversão deEᵥparaQᵥ.
Caso as sequências de tipo de parâmetro {P₁, P₂, ..., Pᵥ} e {Q₁, Q₂, ..., Qᵥ} sejam equivalentes (ou seja, cada Pᵢ tem uma conversão de identidade para o Qᵢcorrespondente), as seguintes regras de desempate são aplicadas, a fim de determinar o melhor membro da função.
- Se
Mᵢé um método não genérico eMₑé um método genérico, entãoMᵢé melhor do queMₑ. - Caso contrário, se
Mᵢé aplicável na sua forma normal eMₑtem uma coleção de parâmetros e é aplicável apenas na sua forma expandida, entãoMᵢé melhor do queMₑ. - Caso contrário, se ambos os métodos têm coleções de parâmetros e são aplicáveis apenas em suas formas expandidas, e se a coleção de params de
Mᵢtem menos elementos do que a coleção de params deMₑ, entãoMᵢé melhor do queMₑ. - Caso contrário, se
Mᵥtiver mais tipos de parâmetros específicos do queMₓ, entãoMᵥé melhor do queMₓ. Deixe que{R1, R2, ..., Rn}e{S1, S2, ..., Sn}representem os tipos de parâmetros não instanciados e não expandidos deMᵥeMₓ.Mᵥtipos de parâmetros são mais específicos do queMₓs se, para cada parâmetro,Rxnão for menos específico do queSxe, pelo menos para um parâmetro,Rxfor mais específico do queSx:- Um parâmetro type é menos específico do que um parâmetro non-type.
- Recursivamente, um tipo construído é mais específico do que outro tipo construído (com o mesmo número de argumentos de tipo) se pelo menos um argumento de tipo é mais específico e nenhum argumento de tipo é menos específico do que o argumento de tipo correspondente no outro.
- Um tipo de matriz é mais específico do que outro tipo de matriz (com o mesmo número de dimensões) se o tipo de elemento do primeiro for mais específico do que o tipo de elemento do segundo.
- Caso contrário, se um membro for um operador não levantado e o outro for um operador levantado, o não levantado é melhor.
- Se nenhum membro da função foi encontrado para ser melhor, e todos os parâmetros de
Mᵥtêm um argumento correspondente, enquanto os argumentos padrão precisam ser substituídos por pelo menos um parâmetro opcional emMₓ, entãoMᵥé melhor do queMₓ. - Se para pelo menos um parâmetro
Mᵥusar a melhor opção de passagem de parâmetros (§12.6.4.4) do que o parâmetro correspondente emMₓe nenhum dos parâmetros emMₓusar a melhor opção de passagem de parâmetros do queMᵥ,Mᵥé melhor do queMₓ. -
Caso contrário, se ambos os métodos tiverem coleções de parâmetros e forem aplicáveis apenas em suas formas expandidas, então
Mᵢé melhor do queMₑse o mesmo conjunto de argumentos corresponder aos elementos de coleção para ambos os métodos, e um dos seguintes mantém (isso corresponde a https://github.com/dotnet/csharplang/blob/main/proposals/csharp-13.0/collection-expressions-better-conversion.md):-
ambas as coleções de parâmetros não são do tipo span_type, e existe uma conversão implícita de coleções de parâmetros de
Mᵢpara coleções de parâmetros deMₑ -
coleção de parâmetros de
MᵢéSystem.ReadOnlySpan<Eᵢ>, e a coleção de parâmetros deMₑéSystem.Span<Eₑ>, e existe uma conversão de identidade deEᵢparaEₑ -
coleção de parâmetros de
MᵢéSystem.ReadOnlySpan<Eᵢ>ouSystem.Span<Eᵢ>, e a coleção de parâmetros deMₑé uma array_or_array_interface__type com tipo de elementoEₑ, e existe uma conversão de identidade deEᵢparaEₑ
-
ambas as coleções de parâmetros não são do tipo span_type, e existe uma conversão implícita de coleções de parâmetros de
- Caso contrário, nenhum membro da função é superior.
A razão pela qual a nova regra de desempate é colocada no final da lista é o último subitem
- ambas as coleções de parâmetros não são do tipo span_type, e existe uma conversão implícita de coleções de parâmetros de
Mᵢpara coleções de parâmetros deMₑ
é aplicável a matrizes e, portanto, executar o tie-break mais cedo introduzirá uma alteração de comportamento para cenários existentes.
Por exemplo:
class Program
{
static void Main()
{
Test(1);
}
static void Test(in int x, params C2[] y) {} // There is an implicit conversion from `C2[]` to `C1[]`
static void Test(int x, params C1[] y) {} // Better candidate because of "better parameter-passing choice"
}
class C1 {}
class C2 : C1 {}
Se qualquer uma das regras de desempate anteriores se aplicar (incluindo a regra "melhores conversões de argumentos"), o resultado da resolução de sobrecarga pode ser diferente em comparação com o caso em que uma expressão de coleção explícita é usada como argumento.
Por exemplo:
class Program
{
static void Test1()
{
M1(['1', '2', '3']); // IEnumerable<char> overload is used because `char` is an exact match
M1('1', '2', '3'); // IEnumerable<char> overload is used because `char` is an exact match
}
static void M1(params IEnumerable<char> value) {}
static void M1(params System.ReadOnlySpan<MyChar> value) {}
class MyChar
{
private readonly int _i;
public MyChar(int i) { _i = i; }
public static implicit operator MyChar(int i) => new MyChar(i);
public static implicit operator char(MyChar c) => (char)c._i;
}
static void Test2()
{
M2([1]); // Span overload is used
M2(1); // Array overload is used, not generic
}
static void M2<T>(params System.Span<T> y){}
static void M2(params int[] y){}
static void Test3()
{
M3("3", ["4"]); // Ambiguity, better-ness of argument conversions goes in opposite directions.
M3("3", "4"); // Ambiguity, better-ness of argument conversions goes in opposite directions.
// Since parameter types are different ("object, string" vs. "string, object"), tie-breaking rules do not apply
}
static void M3(object x, params string[] y) {}
static void M3(string x, params Span<object> y) {}
}
No entanto, a nossa principal preocupação são os cenários em que as sobrecargas diferem apenas pelo tipo de coleção de params, mas esses tipos de coleção têm o mesmo tipo de elemento. O comportamento deve ser consistente com as expressões explícitas de recolha para esses casos.
A condição "se o mesmo conjunto de argumentos corresponder aos elementos de coleção para ambos os métodos" é importante para cenários como:
class Program
{
static void Main()
{
Test(x: 1, y: 2); // Ambiguous
}
static void Test(int x, params System.ReadOnlySpan<int> y) {}
static void Test(int y, params System.Span<int> x) {}
}
Não parece razoável "comparar" coleções que são construídas a partir de elementos diferentes.
Esta seção foi revisada em LDM e foi aprovada.
Um efeito dessas regras é que, quando params de diferentes tipos de elementos são expostos, eles serão ambíguos quando chamados com uma lista de argumentos vazia.
Por exemplo:
class Program
{
static void Main()
{
// Old scenarios
C.M1(); // Ambiguous since params arrays were introduced
C.M1([]); // Ambiguous since params arrays were introduced
// New scenarios
C.M2(); // Ambiguous in C# 13
C.M2([]); // Ambiguous in C# 13
C.M3(); // Ambiguous in C# 13
C.M3([]); // Ambiguous in C# 13
}
public static void M1(params int[] a) {
}
public static void M1(params int?[] a) {
}
public static void M2(params ReadOnlySpan<int> a) {
}
public static void M2(params Span<int?> a) {
}
public static void M3(params ReadOnlySpan<int> a) {
}
public static void M3(params ReadOnlySpan<int?> a) {
}
}
Dado que priorizamos o tipo de elemento acima de tudo, isso parece razoável; Não há nada para dizer ao idioma se o usuário preferiria int? em vez de int neste cenário.
Vinculação dinâmica
Formas expandidas de candidatos que utilizam coleções de parâmetros não-array não serão consideradas como candidatos válidos pelo associador de tempo de execução do C# atual.
Se o primary_expression não tiver o tipo de tempo de compilação dynamic, a invocação do método passa por uma verificação limitada em tempo de compilação, conforme descrito no §12.6.5 Verificação em tempo de compilação da invocação de membro dinâmico.
Se apenas um candidato for aprovado no teste, a convocação do candidato é vinculada estaticamente quando estiverem preenchidas todas as seguintes condições:
- o candidato é uma função local
- ou o candidato não é genérico, ou os seus argumentos de tipo são explicitamente especificados;
- Não existe ambiguidade entre a forma normal e a forma expandida do candidato que não possa ser resolvida em tempo de compilação.
Caso contrário, o invocation_expression é vinculado dinamicamente.
Se apenas um candidato for aprovado no teste acima:
- se esse candidato for uma função local, ocorre um erro em tempo de compilação;
- Se esse candidato for aplicável somente na forma expandida utilizando coleções de parâmetros que não sejam de matriz, ocorrerá um erro em tempo de compilação.
Também devemos considerar reverter/corrigir a violação de especificações que afeta atualmente as funções locais, consulte https://github.com/dotnet/roslyn/issues/71399.
LDM confirmou que queremos corrigir esta violação de especificação.
Árvores de expressão
Não há suporte para expressões de coleção em árvores de expressão. Da mesma forma, formas expandidas de coleções de parâmetros que não são arrays não serão suportadas em árvores de expressão. Não mudaremos a forma como o compilador vincula lambdas para árvores de expressão com o objetivo de evitar o uso de APIs utilizando formas expandidas de coleções de params não array.
Ordem de avaliação com coleções não baseadas em arrays em cenários não triviais
Esta seção foi revisada em LDM e foi aprovada. Apesar do fato de que os casos de matriz se desviam de outras coleções, a especificação de idioma oficial não precisa especificar regras diferentes para matrizes. Os desvios poderiam simplesmente ser tratados como um artefato de implementação. Ao mesmo tempo, não pretendemos alterar o comportamento existente em torno de matrizes.
Argumentos nomeados
Uma instância de coleção é criada e preenchida depois que o argumento lexicamente anterior é avaliado, mas antes que o argumento lexicamente seguinte seja avaliado.
Por exemplo:
class Program
{
static void Main()
{
Test(b: GetB(), c: GetC(), a: GetA());
}
static void Test(int a, int b, params MyCollection c) {}
static int GetA() => 0;
static int GetB() => 0;
static int GetC() => 0;
}
A ordem de avaliação é a seguinte:
-
GetBchama-se -
MyCollectioné criado e preenchido,GetCé chamado no processo -
GetAchama-se -
Testchama-se
Observe que, no caso da matriz params, a matriz é criada antes do método de destino ser invocado, depois que todos os argumentos são avaliados em sua ordem lexical.
Atribuição composta
Uma instância de coleção é criada e preenchida depois que o índice lexicamente anterior é avaliado, mas antes que o índice lexicamente seguinte seja avaliado. A instância é usada para invocar getter e setter do indexador de destino.
Por exemplo:
class Program
{
static void Test(Program p)
{
p[GetA(), GetC()]++;
}
int this[int a, params MyCollection c] { get => 0; set {} }
static int GetA() => 0;
static int GetC() => 0;
}
A ordem de avaliação é a seguinte:
-
GetAé chamado e armazenado em cache -
MyCollectioné criado, preenchido e armazenado em cache,GetCé chamado no processo - O getter do indexador é invocado com valores armazenados em cache para índices
- O resultado é incrementado
- O setter do indexador é invocado com valores de índices armazenados em cache e o resultado do incremento.
Um exemplo com uma coleção vazia:
class Program
{
static void Test(Program p)
{
p[GetA()]++;
}
int this[int a, params MyCollection c] { get => 0; set {} }
static int GetA() => 0;
}
A ordem de avaliação é a seguinte:
-
GetAé chamado e armazenado em cache - Um
MyCollectionvazio é criado e armazenado em cache - O getter do indexador é invocado com valores armazenados em cache para índices
- O resultado é incrementado
- O setter do indexador é invocado com valores de índices armazenados em cache e o resultado do incremento.
Inicializador de objetos
Uma instância de coleção é criada e preenchida depois que o índice lexicamente anterior é avaliado, mas antes que o índice lexicamente seguinte seja avaliado. A instância é usada para invocar o getter do indexador tantas vezes quanto necessário.
Por exemplo:
class C1
{
public int F1;
public int F2;
}
class Program
{
static void Test()
{
_ = new Program() { [GetA(), GetC()] = { F1 = GetF1(), F2 = GetF2() } };
}
C1 this[int a, params MyCollection c] => new C1();
static int GetA() => 0;
static int GetC() => 0;
static int GetF1() => 0;
static int GetF2() => 0;
}
A ordem de avaliação é a seguinte:
-
GetAé chamado e armazenado em cache -
MyCollectioné criado, preenchido e armazenado em cache,GetCé chamado no processo - O getter do indexador é invocado com valores armazenados em cache para índices
-
GetF1é avaliado e atribuído aF1campo deC1reajustado na etapa anterior - O getter do indexador é invocado com valores armazenados em cache para índices
-
GetF2é avaliado e atribuído aF2campo deC1reajustado na etapa anterior
Observe que, no caso de matriz params, seus elementos são avaliados e armazenados em cache, mas uma nova instância de uma matriz (com os mesmos valores dentro) é usada para cada invocação do getter do indexador. Para o exemplo acima, a ordem de avaliação é a seguinte:
-
GetAé chamado e armazenado em cache -
GetCé chamado e armazenado em cache - O getter do indexador é invocado com o
GetAem cache, e uma nova matriz é preenchida com oGetCem cache. -
GetF1é avaliado e atribuído aF1campo deC1reajustado na etapa anterior - O getter do indexador é invocado com o
GetAem cache, e uma nova matriz é preenchida com oGetCem cache. -
GetF2é avaliado e atribuído aF2campo deC1reajustado na etapa anterior
Um exemplo com uma coleção vazia:
class C1
{
public int F1;
public int F2;
}
class Program
{
static void Test()
{
_ = new Program() { [GetA()] = { F1 = GetF1(), F2 = GetF2() } };
}
C1 this[int a, params MyCollection c] => new C1();
static int GetA() => 0;
static int GetF1() => 0;
static int GetF2() => 0;
}
A ordem de avaliação é a seguinte:
-
GetAé chamado e armazenado em cache - Um
MyCollectionvazio é criado e armazenado em cache - O getter do indexador é invocado com valores armazenados em cache para índices
-
GetF1é avaliado e atribuído aF1campo deC1reajustado na etapa anterior - O getter do indexador é invocado com valores armazenados em cache para índices
-
GetF2é avaliado e atribuído aF2campo deC1reajustado na etapa anterior
Ref. segurança
A seção de segurança ref collection expressions é aplicável à construção de coleções de parâmetros quando as APIs são invocadas em sua forma expandida.
Os parâmetros Params são implicitamente scoped quando o seu tipo é uma estrutura de referência (ref struct). UnscopedRefAttribute pode ser usado para substituir isso.
Metadados
Nos metadados, podemos marcar parâmetros params não-array com System.ParamArrayAttribute, da mesma forma que são marcadas as matrizes params hoje.
No entanto, parece que será mais seguro usarmos um atributo diferente para parâmetros params que não sejam arrays.
Por exemplo, o compilador VB atual não será capaz de consumi-los decorados com ParamArrayAttribute nem na forma normal, nem na forma expandida. Portanto, a adição de um modificador 'params' é provável que cause problemas para os consumidores de VB e, muito provavelmente, para consumidores de outras linguagens ou ferramentas.
Sendo assim, parâmetros não matriz params são marcados com um novo System.Runtime.CompilerServices.ParamCollectionAttribute.
namespace System.Runtime.CompilerServices
{
[AttributeUsage(AttributeTargets.Parameter, Inherited = true, AllowMultiple = false)]
public sealed class ParamCollectionAttribute : Attribute
{
public ParamCollectionAttribute() { }
}
}
Esta seção foi revisada em LDM e foi aprovada.
Perguntas abertas
Alocações de pilha
Aqui está uma citação de https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#unresolved-questions: "Alocações de pilha para coleções enormes podem saturar a pilha. Deve o compilador ter uma heurística para colocar esses dados no monte?
A linguagem não deve ser especificada para permitir essa flexibilidade?
Devemos seguir as especificações para params Span<T>." Parece que temos de responder às perguntas no contexto desta proposta.
[Resolvido] scoped parâmetros implícitos
Houve uma sugestão de que, quando params modifica um parâmetro ref struct, ele deve ser considerado como declarado scoped.
Argumenta-se que o número de casos em que se deseja que o parâmetro esteja delimitado é de praticamente 100% ao examinar os casos na BCL. Em alguns casos que precisam disso, a definição padrão pode ser substituída por [UnscopedRef].
No entanto, pode ser indesejável alterar o padrão simplesmente com base na presença do modificador params. Especialmente em cenários de substituições/implementos, o modificador params não precisa corresponder.
Resolução:
Parâmetros são implicitamente delimitados no âmbito - https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-11-15.md#params-improvements.
[Resolvido] Considere aplicar scoped ou params em substituições
Já afirmamos anteriormente que params parâmetros devem ser scoped por padrão. No entanto, isso introduz um comportamento estranho ao substituir, devido às nossas regras existentes acerca de reiterar params:
class Base
{
internal virtual Span<int> M1(scoped Span<int> s1, params Span<int> s2) => throw null!;
}
class Derived : Base
{
internal override Span<int> M1(Span<int> s1, // Error, missing `scoped` on override
Span<int> s2 // Proposal: Error: parameter must include either `params` or `scoped`
) => throw null!;
}
Temos uma diferença de comportamento entre transportar o params e transportar o scoped através de substituições aqui: params é herdado implicitamente, e com ele scoped, enquanto scoped por si só não é herdado implicitamente e deve ser repetido em todos os níveis.
Proposta: Devemos impor que as substituições de parâmetros params devem indicar explicitamente params ou scoped se a definição original for um parâmetro scoped. Em outras palavras, s2 em Derived deve ter params, scopedou ambos.
Resolução:
Exigiremos declarar explicitamente scoped ou params na substituição de um parâmetro params quando um parâmetro nãoparams for necessário para fazê-lo - https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-02-21.md#params-and-scoped-across-overrides.
[Resolvido] A presença de membros obrigatórios deve impedir a declaração do parâmetro params?
Considere o seguinte exemplo:
using System.Collections;
using System.Collections.Generic;
public class MyCollection1 : IEnumerable<long>
{
IEnumerator<long> IEnumerable<long>.GetEnumerator() => throw null;
IEnumerator IEnumerable.GetEnumerator() => throw null;
public void Add(long l) => throw null;
public required int F; // Collection has required member and constructor doesn't initialize it explicitly
}
class Program
{
static void Main()
{
Test(2, 3); // error CS9035: Required member 'MyCollection1.F' must be set in the object initializer or attribute constructor.
}
// Proposal: An error is reported for the parameter indicating that the constructor that is required
// to be available doesn't initialize required members. In other words, one is able
// to declare such a parameter under the specified conditions.
static void Test(params MyCollection1 a)
{
}
}
Resolução:
Vamos validar os membros required em função do construtor que é usado para determinar a elegibilidade para ser um parâmetro params no local da declaração - https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-02-21.md#required-members-and-params-parameters.
Alternativas
Existe uma proposta alternativa que se estende params apenas para ReadOnlySpan<T>.
Além disso, pode-se dizer que, com as expressões de coleção agora na língua, não há necessidade de estender o suporte de params. Para qualquer tipo de coleção. Para consumir uma API com tipo de coleção, um desenvolvedor só precisa adicionar dois caracteres, [ antes da lista expandida de argumentos e ] depois dela. Tendo isso em conta, estender o suporte a params pode ser um exagero, especialmente porque é improvável que outras linguagens suportem tão cedo o consumo de parâmetros params que não sejam matrizes.
Propostas relacionadas
- https://github.com/dotnet/csharplang/issues/1757
- https://github.com/dotnet/csharplang/blob/main/proposals/rejected/format.md#extending-params
Reuniões de design relacionadas
- https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-11-15.md#params-improvements
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-01-08.md
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-01-10.md
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-01-29.md
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-01-31.md#params-collections-evaluation-orders
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-02-21.md#params-collections
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-04-22.md#effect-of-language-version-on-overload-resolution-in-presence-of-params-collections
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-04-24.md#adjust-dynamic-binding-rules-for-a-situation-of-a-single-applicable-candidate
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-05-01.md#adjust-binding-rules-in-the-presence-of-a-single-candidate
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-06-03.md#params-collections-and-dynamic
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-06-12.md#params-span-breaks
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-06-17.md#params-span-breaks
C# feature specifications