C# params
O string.Join(", ", "arroz", "feijão", "café") aceita dois, três, dez textos. Como um método aceita "quantos argumentos vierem", se cada parâmetro precisa de um argumento? Com a palavra params. Ela transforma os argumentos que sobram num array, montado pelo compilador. Nesta aula você vai escrever métodos assim, ver as regras do params e o cuidado que ele exige quando chegam zero argumentos.
- O problema que o
paramsresolve: o mesmo método com 2, 3 ou 10 valores. - Como declarar
params int[] numerose por que, dentro do método,numerosé um array comum. - Que dá para chamar com valores soltos, com um array pronto ou com nada.
- Como misturar parâmetros comuns e
params(e por que oparamsvem por último). - O cuidado com zero argumentos, com a exceção real.
- Os
paramsde coleções, novos no C# 13.
Todos os programas desta aula rodaram na máquina fictícia da Ana, na pasta ~/cs, com o .NET 10 e o comando dotnet run arquivo.cs. Você vai precisar de Arrays e de foreach.
A ideia: a sacola
No caixa da lojinha, cada cliente traz uma quantidade diferente de produtos. O caixa não tem "uma mão para cada produto": ele pega uma sacola e põe nela tudo o que o cliente trouxe, um produto, cinco, nenhum. Depois trabalha com a sacola inteira.
O params é essa sacola. Quem chama passa os valores soltos, separados por vírgula; o C# junta todos num array, e o método recebe esse array.
1. O problema: um número fixo de parâmetros
Para somar as notas de um aluno, a Ana poderia escrever Somar(int a, int b), depois Somar(int a, int b, int c), depois com quatro… sem fim. O caminho natural é receber um array:
Console.WriteLine(Somar(new int[] { 7, 8 }));
Console.WriteLine(Somar(new int[] { 7, 8, 10, 5 }));
int Somar(int[] numeros)
{
int total = 0;
foreach (int n in numeros)
{
total += n;
}
return total;
}
$ dotnet run semparams.cs 15 30
Funciona com qualquer quantidade, mas toda chamada precisa montar o array: new int[] { … }. É comprido e esconde o que importa, os números.
2. A palavra params
Basta uma palavra a mais na declaração:
Console.WriteLine(Somar(7, 8));
Console.WriteLine(Somar(7, 8, 10, 5));
Console.WriteLine(Somar(42));
Console.WriteLine(Somar());
int[] notas = { 6, 9, 3 };
Console.WriteLine(Somar(notas));
int Somar(params int[] numeros)
{
Console.Write($"[{numeros.Length}] ");
int total = 0;
foreach (int n in numeros)
{
total += n;
}
return total;
}
$ dotnet run params1.cs [2] 15 [4] 30 [1] 42 [0] 0 [3] 18
- params int[] numeros
- "Junte todos os argumentos
intque vierem num array chamadonumeros". O tipo tem de ser um array (int[],string[],decimal[]…) ou uma das coleções da seção 5. - Somar(7, 8)
- Valores soltos. O compilador transforma a chamada em
Somar(new int[] { 7, 8 }): é a mesma coisa da seção 1, só que escrita por ele. - Somar(42) e Somar()
- Um valor só vira um array de 1 elemento. Nenhum valor vira um array vazio (
Length= 0), e nãonull. - Somar(notas)
- Um array pronto também serve: ele é usado direto, sem ser copiado para outro array.
- numeros.Length, foreach
- Dentro do método,
numerosé um array comum. O[N]que o programa mostra é o tamanho dele em cada chamada.
Missão: as chamadas passam números soltos, e o programa não compila (CS1501). Acrescente uma palavra na declaração do método para que elas funcionem. A saída deve ser:
15 10
Console.WriteLine(Somar(5, 10));
Console.WriteLine(Somar(1, 2, 3, 4));
int Somar(int[] numeros)
{
int total = 0;
foreach (int n in numeros)
{
total += n;
}
return total;
}
Console.WriteLine(Somar(5, 10));
Console.WriteLine(Somar(1, 2, 3, 4));
int Somar(params int[] numeros)
{
int total = 0;
foreach (int n in numeros)
{
total += n;
}
return total;
}
3. Parâmetros comuns antes do params
Um método pode ter parâmetros comuns e, no fim, um params. Os primeiros argumentos vão para os comuns, por posição, e todos os que sobrarem vão para a sacola:
MostrarLista("Compras", "arroz", "feijão", "café");
MostrarLista("Material", "caderno");
MostrarLista("Vazia");
void MostrarLista(string titulo, params string[] itens)
{
Console.WriteLine($"== {titulo} ({itens.Length}) ==");
foreach (string item in itens)
{
Console.WriteLine($"- {item}");
}
}
$ dotnet run titulo.cs == Compras (3) == - arroz - feijão - café == Material (1) == - caderno == Vazia (0) ==
Na primeira chamada, "Compras" foi para titulo, e os outros três para itens. Na última, só o título: itens veio vazio, e o foreach não rodou nenhuma vez.
O params tem de ser o último
Se o params viesse antes, o compilador não teria como saber onde a sacola termina:
MostrarLista("arroz", "feijão", "Compras");
void MostrarLista(params string[] itens, string titulo)
{
Console.WriteLine(titulo);
}
$ dotnet run paramsfim.cs /home/ana/cs/paramsfim.cs(1,1): error CS1501: Nenhuma sobrecarga para o método "MostrarLista" leva 3 argumentos /home/ana/cs/paramsfim.cs(3,19): error CS0231: Um parâmetro params deve ser o último parâmetro em uma lista de parâmetros Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
O erro que importa é o CS0231 (A params parameter must be the last parameter in a parameter list), na declaração. O CS1501 da chamada é consequência. Pela mesma razão, um método só pode ter um params.
Todos os valores têm de ser do tipo da sacola
$ dotnet run paramstipo.cs « Somar(7, 8, "10") com params int[] /home/ana/cs/paramstipo.cs(1,31): error CS1503: Argumento 3: não é possível converter de "string" para "int" Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
Cada valor solto é conferido como se fosse um argumento comum. Aqui, o terceiro é um texto, e a sacola é de int.
4. O cuidado com zero argumentos
Como Somar() compila, alguém vai chamar o seu método sem nada. Para uma soma, tudo bem: dá 0. Para uma média, não:
Console.WriteLine(Media(7, 8, 9));
Console.WriteLine(Media());
int Media(params int[] notas)
{
int soma = 0;
foreach (int n in notas) soma += n;
return soma / notas.Length;
}
$ dotnet run media0.cs 8 Unhandled exception. System.DivideByZeroException: Attempted to divide by zero. at Program.<<Main>$>g__Media|0_0(Int32[] notas) in /home/ana/cs/media0.cs:line 8 at Program.<Main>$(String[] args) in /home/ana/cs/media0.cs:line 2 $ echo $? 134
A primeira média deu 8. Na segunda, notas.Length é 0, e a divisão de inteiros por zero derruba o programa (Attempted to divide by zero, "tentou dividir por zero"), com o código de saída 134. Repare no caminho: g__Media, a função local, chamada da linha 2. Com double, o programa não quebra, mas mostra NaN ("não é um número"), o que também não ajuda ninguém.
O conserto é testar o tamanho antes:
int Media(params int[] notas)
{
if (notas.Length == 0)
{
return 0;
}
int soma = 0;
foreach (int n in notas) soma += n;
return soma / notas.Length;
}
Outra saída é obrigar pelo menos um valor, com um parâmetro comum antes: int Media(int primeira, params int[] outras). Assim, Media() nem compila.
5. Você já usou params
Vários métodos do .NET usam params:
Console.WriteLine(string.Join(", ", "arroz", "feijão", "café"));
Console.WriteLine(string.Concat("Lo", "ji", "nha"));
Console.WriteLine(Path.Combine("/home", "ana", "cs", "ola.cs"));
Console.WriteLine(Math.Max(3, 9));
$ dotnet run biblioparams.cs arroz, feijão, café Lojinha /home/ana/cs/ola.cs 9
string.Join, string.Concat e Path.Combine aceitam quantos textos você quiser. O Math.Max, não: ele recebe exatamente dois números. Math.Max(3, 9, 5) dá CS1501. Para o maior de vários, use new[] { 3, 9, 5 }.Max() (aula LINQ).
Coleções no params C# 13
Até o C# 12, o params só aceitava arrays. Desde o C# 13 (.NET 9), ele aceita também outras coleções, como List<T> e ReadOnlySpan<T>:
Console.WriteLine(Somar(7, 8, 10));
Console.WriteLine(Contar("a", "b"));
int Somar(params ReadOnlySpan<int> numeros)
{
int total = 0;
foreach (int n in numeros) total += n;
return total;
}
int Contar(params List<string> itens) => itens.Count;
$ dotnet run paramsspan.cs 25 2
A chamada é igual. A diferença está dentro do método: com List<string>, o tamanho é Count (List); com ReadOnlySpan<int>, o .NET pode evitar criar um array novo a cada chamada, o que deixa métodos muito usados mais rápidos. É por isso que, no .NET 9 e 10, métodos como string.Join ganharam versões com params ReadOnlySpan. Para os seus programas, params int[] continua sendo a escolha simples.
Formas diferentes
Na forma com classe, o params funciona igual:
class Program
{
static void Main()
{
Console.WriteLine(Maior(4, 17, 9, 12));
}
static int Maior(params int[] numeros)
{
int maior = numeros[0];
foreach (int n in numeros)
{
if (n > maior) maior = n;
}
return maior;
}
}
$ dotnet run classeparams.cs 17
Atenção: Maior(), sem nada, compilaria e quebraria em numeros[0] com IndexOutOfRangeException. É o cuidado da seção 4 de novo.
Qual usar?
| Situação | Use |
|---|---|
| Quem chama vai escrever poucos valores soltos, em quantidade variável | params T[] |
| Quem chama já tem os valores numa lista ou array | Parâmetro comum T[] ou List<T> (o params também aceita, mas não ajuda) |
| Precisa de pelo menos um valor | (T primeiro, params T[] outros) |
| Método muito chamado, em que cada array conta | params ReadOnlySpan<T> C# 13 |
| Sempre o mesmo número de valores | Parâmetros comuns |
Quando dá errado
error CS0231: Um parâmetro params deve ser o último parâmetro em uma lista de parâmetros
O que significa: há um parâmetro depois do params, ou dois params no mesmo método.
Como consertar: leve o params para o fim e deixe só um. Se você precisa de duas listas, use dois parâmetros array comuns.
error CS1503: Argumento 3: não é possível converter de "string" para "int"
O que significa: um dos valores soltos não é do tipo da sacola.
Como consertar: passe o valor do tipo certo (10, e não "10"). Se o valor vem do teclado, converta antes com int.TryParse.
error CS1501: Nenhuma sobrecarga para o método "Somar" leva 2 argumentos
O que significa: você chamou com valores soltos um método que recebe um array comum, sem params. É o erro da primeira missão.
Como consertar: acrescente params na declaração, ou monte o array na chamada (new int[] { 5, 10 }, ou [5, 10] com as expressões de coleção do C# 12).
System.DivideByZeroException ou IndexOutOfRangeException com zero argumentos
O que significa: o método foi chamado sem nenhum valor, a sacola veio vazia, e o código dividiu por Length ou leu a posição [0].
Como consertar: teste if (numeros.Length == 0) no começo do método, ou exija um primeiro valor com um parâmetro comum antes do params.
Referência rápida
| Escrita | O que faz |
|---|---|
int Somar(params int[] numeros) | Aceita 0 ou mais int; dentro, numeros é um array |
Somar(1, 2, 3) | O compilador monta new int[] { 1, 2, 3 } |
Somar() | Array vazio (Length = 0), nunca null |
Somar(meuArray) | Usa o array pronto |
void M(string t, params string[] r) | O primeiro vai para t; os que sobram, para r |
params ReadOnlySpan<int>, params List<T> | Coleções no params (C# 13) |
| Regras | Só um params, sempre o último parâmetro |
Exercícios
1. A palavra
Complete a declaração para aceitar quantos nomes vierem:
2. Quantos?
Com void M(string titulo, params int[] v), quanto vale v.Length na chamada M("x", 4, 5, 6)?
3. Nada
Com int Contar(params int[] v) => v.Length;, o que Contar() devolve?
4. Qual declaração compila?
5. Dentro de void M(params string[] itens), o que é itens quando a chamada é M()?
6. Por que Media() derrubou o programa com DivideByZeroException?
Missão final: o recibo de cada cliente
Missão: escreva o método MostrarRecibo. O primeiro parâmetro é o nome do cliente (string); depois vêm os preços dos produtos, quantos forem, num params decimal[]. Ele mostra uma linha com o nome, quantos itens vieram e o total, com :C. As chamadas já estão prontas. A saída deve ser:
Cliente: Ana | itens: 3 | total: R$ 18,25 Cliente: Bruno | itens: 1 | total: R$ 4,50 Cliente: Carla | itens: 0 | total: R$ 0,00
MostrarRecibo("Ana", 12.50m, 3m, 2.75m);
MostrarRecibo("Bruno", 4.50m);
MostrarRecibo("Carla");
// escreva o método MostrarRecibo aqui
MostrarRecibo("Ana", 12.50m, 3m, 2.75m);
MostrarRecibo("Bruno", 4.50m);
MostrarRecibo("Carla");
void MostrarRecibo(string cliente, params decimal[] precos)
{
decimal total = 0;
foreach (decimal preco in precos)
{
total += preco;
}
Console.WriteLine($"Cliente: {cliente} | itens: {precos.Length} | total: {total:C}");
}
Resumo
paramsdeixa um método aceitar quantos argumentos vierem: o compilador junta os valores soltos num array.- Dentro do método, o parâmetro é um array comum (
Length,foreach). A chamada pode passar valores soltos, um array pronto ou nada (array vazio, nuncanull). - Parâmetros comuns vêm antes; o
paramsé o último e único (CS0231). Cada valor tem de ser do tipo da sacola (CS1503). - Cuide do caso de zero argumentos: dividir por
LengthdáDivideByZeroException; ler[0]dáIndexOutOfRangeException. - Desde o C# 13, o
paramsaceita coleções comoList<T>eReadOnlySpan<T>.