C# Parâmetros

Na aula Métodos, todo método fazia sempre a mesma coisa: Linha() desenhava sempre a mesma linha. Um método fica muito mais útil quando você pode entregar informações para ele trabalhar: "mostre o dobro deste número", "escreva o preço deste produto". Essas informações chegam pelos parâmetros. Nesta aula você vai escrever métodos que recebem valores, entender por que o método recebe uma cópia (e não a variável original) e ler os erros que aparecem quando a chamada não combina com a declaração.

Você vai aprender
  • A diferença entre parâmetro (na declaração) e argumento (na chamada).
  • Como escrever métodos com um ou vários parâmetros, cada um com o seu tipo.
  • Que argumento pode ser um valor, uma variável ou uma conta.
  • Por que a ordem dos argumentos importa, e quais conversões o C# faz sozinho.
  • A passagem por valor: o método recebe uma cópia, e mudar o parâmetro não muda a variável de quem chamou.
  • O caso dos arrays: por que o método consegue mudar os elementos.
  • Os erros CS7036, CS1501, CS1503, CS1001 e CS0136.

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. Como em Métodos, as instruções ficam no topo do arquivo e os métodos no fim.


A ideia: o formulário e o que você preenche

Pense no formulário de pedido da lojinha da Ana. O papel impresso tem campos com nome: Produto: ____, Preço: ____. Cada cliente preenche os campos com valores diferentes, mas o formulário é sempre o mesmo.

No formulárioEm C#Nome técnico
O campo impresso, "Preço: ____"A variável declarada entre os parênteses da declaração: void MostrarPreco(decimal preco)parâmetro
O que o cliente escreve no campo, "12,50"O valor entre os parênteses da chamada: MostrarPreco(12.50m);argumento

Muita gente usa as duas palavras como se fossem a mesma coisa. Não tem problema; mas, quando o compilador fala em argumento, ele quer dizer o valor da chamada, e quando fala em parâmetro, o nome da declaração.


1. Um método com um parâmetro

MostrarDobro(4);
MostrarDobro(10);
MostrarDobro(-3);

void MostrarDobro(int n)
{
    Console.WriteLine($"O dobro de {n} é {n * 2}");
}
$ dotnet run dobro.cs
O dobro de 4 é 8
O dobro de 10 é 20
O dobro de -3 é -6
void MostrarDobro(int n)
A declaração. Dentro dos parênteses está o parâmetro: um tipo (int) e um nome (n), igual a uma declaração de variável, só que sem o ; e sem valor.
n
Dentro do corpo, n é uma variável comum: dá para ler, fazer contas, mostrar. Ela existe só enquanto o método roda; fora dele, n não existe (a aula Escopo explica).
MostrarDobro(4);
A chamada. O argumento 4 é copiado para n antes de o corpo começar. É como se o método começasse com int n = 4;.

As três chamadas usam o mesmo método com três valores diferentes. Na primeira, n vale 4; na segunda, 10; na terceira, -3. Cada chamada começa do zero, com o seu próprio n.

Os nomes de parâmetros seguem a convenção das variáveis: camelCase, começando com minúscula (preco, nomeDoCliente). O nome do método, como você viu, é PascalCase.

Missão: escreva no fim do arquivo um método Saudar que recebe um string chamado nome e mostra Olá, seguido do nome e de !. As duas chamadas já estão prontas. A saída deve ser:

Olá, Ana!
Olá, Bruno!
Saudar("Ana");
Saudar("Bruno");

// escreva o método Saudar aqui
Saudar("Ana");
Saudar("Bruno");

void Saudar(string nome)
{
    Console.WriteLine($"Olá, {nome}!");
}

2. Vários parâmetros

Para mostrar uma linha do recibo, a Ana precisa de duas informações: o produto e o preço. Os parâmetros ficam separados por vírgula, cada um com o seu tipo:

MostrarItem("Caderno", 12.50m);
MostrarItem("Caneta", 3m);
MostrarItem("Mochila", 89.90m);

void MostrarItem(string produto, decimal preco)
{
    Console.WriteLine($"{produto,-10} R$ {preco,7:N2}");
}
$ dotnet run item.cs
Caderno    R$   12,50
Caneta     R$    3,00
Mochila    R$   89,90
(string produto, decimal preco)
Dois parâmetros: um texto e um número com casas decimais. Dinheiro, no guia, é sempre decimal (float, double e decimal).
MostrarItem("Caderno", 12.50m)
Os argumentos vão na mesma ordem dos parâmetros: o primeiro ("Caderno") vai para produto, o segundo (12.50m) vai para preco. O m diz que o número é decimal.
{produto,-10} e {preco,7:N2}
Só formatação: o produto ocupa 10 casas alinhado à esquerda, e o preço ocupa 7 casas com 2 decimais (Formatar Números). Foi o que deixou as colunas alinhadas.

Cada parâmetro precisa do seu próprio tipo, mesmo quando os tipos são iguais. Numa declaração de variáveis você pode escrever int a, b;, mas nos parâmetros não:

Somar(2, 3);

void Somar(int a, b)
{
    Console.WriteLine(a + b);
}
$ dotnet run tipojunto.cs
/home/ana/cs/tipojunto.cs(3,20): error CS1001: Identificador esperado

Ocorreu uma falha no build. Corrija os erros de build e execute novamente.

Identificador esperado (Identifier expected): identificador é um nome. O compilador leu o b como se fosse um tipo e ficou esperando o nome do parâmetro logo depois; na coluna 20 encontrou o ). Conserto: void Somar(int a, int b).

O argumento pode ser uma variável ou uma conta

Um argumento não precisa ser um valor escrito na hora. Qualquer coisa que produza um valor do tipo certo serve: uma variável, uma conta, o resultado de outro método.

string nome = "Borracha";
decimal precoUnitario = 1.75m;
int quantidade = 4;

MostrarTotal(nome, precoUnitario, quantidade);
MostrarTotal("Lápis", 0.90m, 10);
MostrarTotal(nome.ToUpper(), precoUnitario * 2, quantidade + 1);

void MostrarTotal(string produto, decimal preco, int qtd)
{
    decimal total = preco * qtd;
    Console.WriteLine($"{qtd} x {produto} = {total:C}");
}
$ dotnet run total.cs
4 x Borracha = R$ 7,00
10 x Lápis = R$ 9,00
5 x BORRACHA = R$ 17,50
ChamadaO que chega em produtoprecoqtd
1ª: variáveiso valor de nome: "Borracha"1,754
2ª: valores escritos"Lápis"0,9010
3ª: contas e métodosnome.ToUpper() = "BORRACHA"1,75 * 2 = 3,504 + 1 = 5

Antes de pular para o método, o C# calcula cada argumento, da esquerda para a direita, e só então copia os resultados para os parâmetros. Repare também que os nomes não precisam combinar: a variável se chama quantidade e o parâmetro, qtd. O que liga um ao outro é a posição.


3. A ordem e o tipo dos argumentos

Como quem decide é a posição, trocar a ordem é um erro, e o compilador pega:

MostrarItem(12.50m, "Caderno");

void MostrarItem(string produto, decimal preco)
{
    Console.WriteLine($"{produto}: {preco:C}");
}
$ dotnet run ordemerr.cs
/home/ana/cs/ordemerr.cs(1,13): error CS1503: Argumento 1: não é possível converter de "decimal" para "string"
/home/ana/cs/ordemerr.cs(1,21): error CS1503: Argumento 2: não é possível converter de "string" para "decimal"

Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
Argumento 1
O primeiro valor da chamada, 12.50m, na coluna 13.
não é possível converter de "decimal" para "string"
O primeiro parâmetro é string, e um decimal não vira texto sozinho. Em inglês: Argument 1: cannot convert from 'decimal' to 'string'.
Argumento 2
O mesmo problema ao contrário, no segundo valor.

Quando os dois parâmetros têm o mesmo tipo, o compilador não tem como perceber a troca. Dividir(10, 2) e Dividir(2, 10) compilam os dois, com resultados diferentes. Por isso vale escolher nomes claros e, quando houver dúvida, usar argumentos nomeados, como Dividir(dividendo: 10, divisor: 2) (aula Parâmetros Opcionais e Nomeados).

Conversões que o C# faz sozinho

O argumento não precisa ser exatamente do tipo do parâmetro: basta existir uma conversão implícita, aquela que nunca perde informação (Conversão de Tipos). Um int cabe num decimal, mas um double não vira decimal sem você pedir:

MostrarPreco(10);
MostrarPreco(12.5);

void MostrarPreco(decimal preco)
{
    Console.WriteLine(preco.ToString("C"));
}
$ dotnet run conv.cs
/home/ana/cs/conv.cs(2,14): error CS1503: Argumento 1: não é possível converter de "double" para "decimal"

Ocorreu uma falha no build. Corrija os erros de build e execute novamente.

A linha 1 (10, um int) passou. A linha 2 não: 12.5 sem letra é double. Com o m, funciona:

$ dotnet run convok.cs                « o mesmo programa, com MostrarPreco(12.5m)
R$ 10,00
R$ 12,50

Argumentos de menos ou de mais

Cada parâmetro precisa receber um argumento (a não ser os opcionais). Faltou um:

MostrarItem("Caderno");

void MostrarItem(string produto, decimal preco)
{
    Console.WriteLine($"{produto}: {preco:C}");
}
$ dotnet run poucos.cs
/home/ana/cs/poucos.cs(1,1): error CS7036: Não há nenhum argumento fornecido que corresponda ao parâmetro necessário 'preco' de 'MostrarItem(string, decimal)'

Ocorreu uma falha no build. Corrija os erros de build e execute novamente.

A mensagem é ótima: diz qual parâmetro ficou vazio (preco) e mostra a lista de tipos que o método espera, (string, decimal). Em inglês: There is no argument given that corresponds to the required parameter 'preco'. Com um argumento a mais (MostrarItem("Caderno", 12.50m, 3)), o erro é o que você já viu em Métodos:

$ dotnet run muitos.cs
/home/ana/cs/muitos.cs(1,1): error CS1501: Nenhuma sobrecarga para o método "MostrarItem" leva 3 argumentos

Ocorreu uma falha no build. Corrija os erros de build e execute novamente.

4. O método recebe uma cópia

Esta é a parte mais importante da aula. Quando você passa uma variável para um método, o método recebe uma cópia do valor, e não a variável. Se ele mudar o parâmetro, muda só a cópia:

int pontos = 10;
Console.WriteLine($"Antes:  pontos = {pontos}");
Dobrar(pontos);
Console.WriteLine($"Depois: pontos = {pontos}");

void Dobrar(int valor)
{
    valor = valor * 2;
    Console.WriteLine($"Dentro: valor  = {valor}");
}
$ dotnet run copia.cs
Antes:  pontos = 10
Dentro: valor  = 20
Depois: pontos = 10                  « continua 10
  1. pontos nasce com 10.
  2. Na chamada Dobrar(pontos), o C# lê o valor de pontos (10) e o copia para o parâmetro valor. Agora existem duas variáveis, cada uma com o seu 10.
  3. Dentro do método, valor vira 20. A variável pontos nem fica sabendo.
  4. O método termina e valor deixa de existir. pontos continua com 10.

Pense numa fotocópia: a Ana entrega uma cópia do boletim à secretaria; se a secretaria rabiscar a cópia, o boletim original da Ana continua limpo. Isso se chama passagem por valor e é o padrão do C#. Ela é uma proteção: você chama um método sem medo de ele estragar as suas variáveis.

E se o parâmetro tiver o mesmo nome da variável? Continua sendo uma cópia:

int pontos = 10;
Dobrar(pontos);
Console.WriteLine($"Fora:   pontos = {pontos}");

void Dobrar(int pontos)
{
    pontos = pontos * 2;
    Console.WriteLine($"Dentro: pontos = {pontos}");
}
$ dotnet run mesmonome.cs
Dentro: pontos = 20
Fora:   pontos = 10

São duas variáveis diferentes que só têm o mesmo nome: o parâmetro pontos do método e a variável pontos do programa. Dentro do método, o nome pontos quer dizer o parâmetro. O guia evita isso, porque confunde quem lê, mas é bom saber que é permitido. A aula Escopo volta ao assunto.

E se eu quiser que o método mude a minha variável?

O jeito mais comum é o método devolver o novo valor, e você guardar: pontos = Dobrar(pontos);. É o assunto da próxima aula, Valor de Retorno. Existe também um jeito de passar a própria variável, e não a cópia: as palavras ref e out, em ref, out e in.

Experimente: rode e confira que pontos continua 10. Depois troque o nome do parâmetro valor por pontos (nas três linhas do método) e rode de novo: o resultado não muda.

int pontos = 10;
Console.WriteLine($"Antes:  pontos = {pontos}");
Dobrar(pontos);
Console.WriteLine($"Depois: pontos = {pontos}");

void Dobrar(int valor)
{
    valor = valor * 2;
    Console.WriteLine($"Dentro: valor  = {valor}");
}

Arrays: a cópia é do endereço

Com arrays, a história muda um pouco. Um array não mora dentro da variável: a variável guarda só o endereço de onde os elementos estão (por isso arrays são chamados de tipo de referência; veja Valor × Referência). Ao passar um array, o método recebe uma cópia desse endereço, e com ele chega aos mesmos elementos:

int[] notas = { 6, 7, 8 };
DarBonus(notas);
Console.WriteLine(string.Join(", ", notas));

void DarBonus(int[] lista)
{
    for (int i = 0; i < lista.Length; i++)
    {
        lista[i] = lista[i] + 1;
    }
}
$ dotnet run arrmuda.cs
7, 8, 9                              « as notas originais mudaram

É como entregar à secretaria uma cópia do endereço da casa da Ana, e não uma cópia da casa: quem tem o endereço consegue entrar e mudar os móveis. Mas trocar o endereço anotado no papel não muda a casa da Ana:

int[] notas = { 6, 7, 8 };
Trocar(notas);
Console.WriteLine(string.Join(", ", notas));

void Trocar(int[] lista)
{
    lista = new int[] { 10, 10, 10 };
    Console.WriteLine("Dentro: " + string.Join(", ", lista));
}
$ dotnet run arrtroca.cs
Dentro: 10, 10, 10
6, 7, 8                              « o array original continua o mesmo
O que o método faz com o parâmetroint, decimal, bool…array
Muda o parâmetro inteiro (valor = 20;, lista = new int[3];)Não afeta quem chamouNão afeta quem chamou
Muda um elemento (lista[0] = 99;)(não existe)Afeta quem chamou: é o mesmo array

Formas diferentes

Parâmetros funcionam igual nas duas formas de escrever métodos que você viu em Métodos. Na forma com classe:

class Program
{
    static void Main()
    {
        MostrarItem("Caderno", 12.50m);
        MostrarItem("Caneta", 3m);
    }

    static void MostrarItem(string produto, decimal preco)
    {
        Console.WriteLine($"{produto}: {preco:C}");
    }
}
$ dotnet run classeparam.cs
Caderno: R$ 12,50
Caneta: R$ 3,00

Os parâmetros ainda podem ganhar recursos extras, cada um com a sua aula:

RecursoCaraPara quêAula
Parâmetro comum (esta aula)decimal precoReceber uma cópia do valoresta
Opcionalint qtd = 1Permitir que a chamada omita o argumentoOpcionais e Nomeados
Argumento nomeadoMostrarItem(preco: 3m, produto: "Caneta")Deixar a chamada clara e livre da ordemOpcionais e Nomeados
ref, out, inref int pontosPassar a própria variável, não uma cópiaref, out e in
paramsparams int[] notasAceitar quantos argumentos vieremparams

Quando dá errado

error CS7036: Não há nenhum argumento fornecido que corresponda ao parâmetro necessário 'preco'…

O que significa: a chamada tem argumentos de menos. A mensagem diz qual parâmetro ficou sem valor e mostra a lista de tipos esperada.

Como consertar: passe um argumento para cada parâmetro, na ordem: MostrarItem("Caderno", 12.50m);.

error CS1501: Nenhuma sobrecarga para o método "MostrarItem" leva 3 argumentos

O que significa: argumentos demais. Nenhuma versão do método recebe essa quantidade.

Como consertar: tire o argumento que sobrou, ou acrescente o parâmetro que falta na declaração.

error CS1503: Argumento 1: não é possível converter de "decimal" para "string"

O que significa: o argumento daquela posição tem um tipo que não combina com o parâmetro. Na maioria das vezes, a ordem foi trocada. Também acontece com 12.5 (um double) indo para um decimal, ou com um texto como "3" indo para um int.

Como consertar: confira a ordem dos parâmetros. Para dinheiro, escreva o m (12.5m). Para transformar texto em número, use int.TryParse (Parse e TryParse).

error CS1001: Identificador esperado
$ dotnet run semtipo.cs
/home/ana/cs/semtipo.cs(3,20): error CS1001: Identificador esperado

Ocorreu uma falha no build. Corrija os erros de build e execute novamente.

O que significa: faltou o tipo de um parâmetro. Aqui a declaração foi void MostrarDobro(n): o compilador entendeu n como um tipo e esperava o nome logo depois. O mesmo erro aparece em (int a, b).

Como consertar: todo parâmetro tem tipo e nome: void MostrarDobro(int n), void Somar(int a, int b).

error CS0136: Um local ou um parâmetro denominado "n" não pode ser declarado neste escopo…
$ dotnet run redecl.cs
/home/ana/cs/redecl.cs(5,9): error CS0136: Um local ou um parâmetro denominado "n" não pode ser declarado neste escopo porque esse nome é usado em um escopo delimitador de local para definir um local ou parâmetro

Ocorreu uma falha no build. Corrija os erros de build e execute novamente.

O que significa: dentro do método MostrarDobro(int n), a linha 5 tenta criar outra variável int n = 2;. O parâmetro já é uma variável com esse nome.

Como consertar: se queria mudar o valor, tire o tipo: n = 2;. Se queria outra variável, dê outro nome.

error CS0103: O nome "n" não existe no contexto atual (fora do método)
$ dotnet run foradeescopo.cs
/home/ana/cs/foradeescopo.cs(2,19): error CS0103: O nome "n" não existe no contexto atual

Ocorreu uma falha no build. Corrija os erros de build e execute novamente.

O que significa: a linha 2, fora do método, tentou mostrar n, mas o parâmetro só existe dentro de MostrarDobro.

Como consertar: se você precisa de um valor calculado pelo método, faça o método devolvê-lo (Valor de Retorno).


Referência rápida

EscritaO que faz
void Nome(int n) { … }Declara um método com um parâmetro int chamado n
void Nome(string a, decimal b)Vários parâmetros, separados por vírgula, cada um com o seu tipo
Nome(5);Chama passando o argumento 5, que é copiado para n
Nome(x * 2, "oi");Argumentos podem ser contas, variáveis e resultados de métodos; são calculados antes da chamada
Ordem dos argumentosIgual à ordem dos parâmetros (a não ser com argumentos nomeados)
Passagem por valorO método recebe uma cópia; mudar o parâmetro não muda a variável de quem chamou
Parâmetro arrayA cópia é do endereço: mudar um elemento muda o array original

Exercícios

1. Declarar o parâmetro

Complete a declaração de um método que recebe uma idade inteira chamada idade:

void MostrarIdade()

2. Dois parâmetros

Complete a declaração para receber um nome (string nome) e uma nota (double nota), nessa ordem:

void MostrarNota()

3. Quanto vale?

Depois de int x = 5; Zerar(x);, com void Zerar(int n) { n = 0; }, quanto vale x?

4. Em MostrarItem("Caneta", 3m), como se chamam "Caneta" e 3m?

5. O método é void MostrarPreco(decimal preco). Qual chamada não compila?

6. int[] v = { 1, 2 }; e void Mudar(int[] a) { a[0] = 9; }. Depois de Mudar(v);, o que tem em v?

Missão final: o recibo com totais

Missão: escreva o método MostrarTotal, com três parâmetros, nesta ordem: o produto (string), o preço de uma unidade (decimal) e a quantidade (int). Ele calcula o total (preço × quantidade) e mostra a linha no formato abaixo, com o total em :C. As chamadas já estão prontas. A saída deve ser exatamente:

3 x Caderno = R$ 37,50
2 x Caneta = R$ 6,00
10 x Lápis = R$ 9,00
MostrarTotal("Caderno", 12.50m, 3);
MostrarTotal("Caneta", 3m, 2);
MostrarTotal("Lápis", 0.90m, 10);

// escreva o método MostrarTotal aqui
MostrarTotal("Caderno", 12.50m, 3);
MostrarTotal("Caneta", 3m, 2);
MostrarTotal("Lápis", 0.90m, 10);

void MostrarTotal(string produto, decimal preco, int quantidade)
{
    decimal total = preco * quantidade;
    Console.WriteLine($"{quantidade} x {produto} = {total:C}");
}

Se apareceu CS1503, confira a ordem e os tipos dos parâmetros. Se o total saiu sem o R$, faltou o :C dentro das chaves.


Resumo

  • Parâmetro é a variável declarada nos parênteses do método; argumento é o valor passado na chamada.
  • Cada parâmetro tem tipo e nome (camelCase); vários são separados por vírgula, cada um com o seu tipo.
  • Argumentos vão na ordem dos parâmetros. Podem ser valores, variáveis ou contas, e o tipo tem de combinar (ou ter conversão implícita, como int → decimal).
  • O método recebe uma cópia do valor (passagem por valor): mudar o parâmetro não muda a variável de quem chamou. Com arrays, a cópia é do endereço, e os elementos podem mudar.
  • Erros típicos: CS7036 (faltou argumento), CS1501 (argumento a mais), CS1503 (tipo ou ordem errada), CS1001 (faltou o tipo do parâmetro), CS0136 (variável com o nome do parâmetro).