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.

Você vai aprender
  • O problema que o params resolve: o mesmo método com 2, 3 ou 10 valores.
  • Como declarar params int[] numeros e 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 o params vem por último).
  • O cuidado com zero argumentos, com a exceção real.
  • Os params de 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 int que vierem num array chamado numeros". 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ão null.
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çãoUse
Quem chama vai escrever poucos valores soltos, em quantidade variávelparams T[]
Quem chama já tem os valores numa lista ou arrayParâ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 contaparams ReadOnlySpan<T> C# 13
Sempre o mesmo número de valoresParâ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

EscritaO 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)
RegrasSó um params, sempre o último parâmetro

Exercícios

1. A palavra

Complete a declaração para aceitar quantos nomes vierem:

void Convidar( string[] nomes)

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

  • params deixa 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, nunca null).
  • 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 Length dá DivideByZeroException; ler [0] dá IndexOutOfRangeException.
  • Desde o C# 13, o params aceita coleções como List<T> e ReadOnlySpan<T>.