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.
- 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,CS1001eCS0136.
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ário | Em 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,nnão existe (a aula Escopo explica). - MostrarDobro(4);
- A chamada. O argumento
4é copiado paranantes de o corpo começar. É como se o método começasse comint 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 paraproduto, o segundo (12.50m) vai parapreco. Omdiz 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
| Chamada | O que chega em produto | preco | qtd |
|---|---|---|---|
| 1ª: variáveis | o valor de nome: "Borracha" | 1,75 | 4 |
| 2ª: valores escritos | "Lápis" | 0,90 | 10 |
| 3ª: contas e métodos | nome.ToUpper() = "BORRACHA" | 1,75 * 2 = 3,50 | 4 + 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 umdecimalnã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
pontosnasce com 10.- Na chamada
Dobrar(pontos), o C# lê o valor depontos(10) e o copia para o parâmetrovalor. Agora existem duas variáveis, cada uma com o seu 10. - Dentro do método,
valorvira 20. A variávelpontosnem fica sabendo. - O método termina e
valordeixa de existir.pontoscontinua 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.
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âmetro | int, decimal, bool… | array |
|---|---|---|
Muda o parâmetro inteiro (valor = 20;, lista = new int[3];) | Não afeta quem chamou | Nã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:
| Recurso | Cara | Para quê | Aula |
|---|---|---|---|
| Parâmetro comum (esta aula) | decimal preco | Receber uma cópia do valor | esta |
| Opcional | int qtd = 1 | Permitir que a chamada omita o argumento | Opcionais e Nomeados |
| Argumento nomeado | MostrarItem(preco: 3m, produto: "Caneta") | Deixar a chamada clara e livre da ordem | Opcionais e Nomeados |
ref, out, in | ref int pontos | Passar a própria variável, não uma cópia | ref, out e in |
params | params int[] notas | Aceitar quantos argumentos vierem | params |
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
| Escrita | O 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 argumentos | Igual à ordem dos parâmetros (a não ser com argumentos nomeados) |
| Passagem por valor | O método recebe uma cópia; mudar o parâmetro não muda a variável de quem chamou |
| Parâmetro array | A 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:
2. Dois parâmetros
Complete a declaração para receber um nome (string nome) e uma nota (double nota), nessa ordem:
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).