C# Valor de Retorno

Os métodos das aulas anteriores faziam alguma coisa: escreviam uma linha, mostravam um preço. Muitas vezes, porém, você quer que o método calcule algo e entregue o resultado de volta, para você guardar numa variável, somar com outro valor ou mostrar do jeito que quiser. Esse resultado é o valor de retorno, e a palavra que o entrega é return ("devolver"). Nesta aula você vai trocar o void por um tipo, usar o resultado de várias formas e ler os erros que o compilador mostra quando falta (ou sobra) um return.

Você vai aprender
  • Como declarar um método que devolve um valor: o tipo no lugar do void e o return.
  • As formas de usar o resultado: numa variável, numa conta, direto no WriteLine, como argumento de outra chamada.
  • Que o return encerra o método na hora, e como usar vários return com if.
  • O return; sozinho, para sair cedo de um método void.
  • O corpo curto com =>.
  • Os erros CS0161, CS0126, CS0127, CS0664, CS0815 e o aviso CS0162.

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.


A ideia: perguntar e receber a resposta

Na lojinha, a Ana entrega os produtos ao caixa e pergunta: "quanto deu?". O caixa faz a conta e responde um número. Com esse número na mão, a Ana decide o que fazer: anotar no caderno, somar com a compra de ontem, dizer ao cliente.

No caixaEm C#
Entregar os produtosPassar os argumentos (Parâmetros)
O caixa faz a contaO corpo do método roda
O caixa responde "deu R$ 37,50"return total;: o método devolve o valor
A Ana anota o número no cadernodecimal compra = Total(…);: quem chamou guarda o valor

Você já usa métodos que devolvem valores: Math.Sqrt(16) devolve 4, texto.ToUpper() devolve o texto em maiúsculas, Console.ReadLine() devolve o que a pessoa digitou. Agora você vai escrever os seus.


1. Trocar o void por um tipo

int resultado = Dobro(4);
Console.WriteLine(resultado);

int Dobro(int n)
{
    return n * 2;
}
$ dotnet run dobro1.cs
8
int Dobro(int n)
O primeiro int, antes do nome, é o tipo de retorno: diz que o método devolve um número inteiro. Ele ocupa o lugar onde antes ficava o void. O segundo int, entre os parênteses, é o tipo do parâmetro, como você já viu.
return n * 2;
"Devolva o valor de n * 2". O C# calcula a conta e entrega o resultado a quem chamou. O valor depois do return tem de ser do tipo de retorno (aqui, int).
int resultado = Dobro(4);
A chamada Dobro(4) vale 8. É como se a linha fosse int resultado = 8;. O valor fica guardado em resultado.

Compare com o MostrarDobro de Parâmetros: aquele método escrevia o dobro na tela e acabava. Este devolve o dobro e não escreve nada. Quem decide o que fazer com o número é quem chamou. Isso deixa o método mais útil, porque o mesmo Dobro serve para mostrar, para somar, para comparar.

Quatro jeitos de usar o resultado

Uma chamada a um método com retorno é uma expressão: ela produz um valor, como 2 + 3. Por isso pode aparecer em qualquer lugar onde caberia um número:

int resultado = Dobro(4);
Console.WriteLine(resultado);
Console.WriteLine(Dobro(10));
Console.WriteLine(Dobro(3) + Dobro(5));
Console.WriteLine(Dobro(Dobro(2)));

int Dobro(int n)
{
    return n * 2;
}
$ dotnet run dobro2.cs
8
20
16
8
LinhaJeitoComo o C# resolve
int resultado = Dobro(4);Guardar numa variávelDobro(4) vale 8; 8 vai para resultado
Console.WriteLine(Dobro(10));Usar direto como argumentoDobro(10) vale 20; 20 vai para o WriteLine
Dobro(3) + Dobro(5)Numa conta6 + 10 = 16
Dobro(Dobro(2))Como argumento do próprio métodoPrimeiro a chamada de dentro: Dobro(2) = 4. Depois Dobro(4) = 8

Missão: escreva o método int Triplo(int n), que devolve (com return) o triplo de n. Ele não escreve nada; quem mostra é o programa. A saída deve ser:

12
30
Console.WriteLine(Triplo(4));
Console.WriteLine(Triplo(10));

// escreva o método Triplo aqui
Console.WriteLine(Triplo(4));
Console.WriteLine(Triplo(10));

int Triplo(int n)
{
    return n * 3;
}

2. Um exemplo da lojinha

O método Total recebe o preço e a quantidade e devolve quanto deu. O programa guarda cada resultado e ainda soma os dois:

decimal caderno = Total(12.50m, 3);
decimal caneta = Total(3m, 2);
decimal compra = caderno + caneta;

Console.WriteLine($"Cadernos: {caderno:C}");
Console.WriteLine($"Canetas:  {caneta:C}");
Console.WriteLine($"Compra:   {compra:C}");

decimal Total(decimal preco, int quantidade)
{
    decimal total = preco * quantidade;
    return total;
}
$ dotnet run total2.cs
Cadernos: R$ 37,50
Canetas:  R$ 6,00
Compra:   R$ 43,50

O tipo de retorno é decimal, porque é dinheiro. Dentro do método, a variável total guarda a conta, e o return total; devolve o valor dela (37,50 na primeira chamada). A variável total deixa de existir quando o método termina; o valor, não: ele já foi entregue e está guardado em caderno.

O jeito certo de "mudar" uma variável

Em Parâmetros você viu que o método recebe uma cópia e não consegue mudar a variável de quem chamou. Com retorno, o método devolve o valor novo, e quem chamou decide guardá-lo:

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

int Dobrar(int valor)
{
    return valor * 2;
}
$ dotnet run pontos2.cs
pontos = 20

Na linha 2, o C# primeiro calcula o lado direito, Dobrar(pontos) = Dobrar(10) = 20, e depois guarda o 20 em pontos. É o mesmo jeito de texto = texto.ToUpper();.

Esquecer de guardar o resultado não dá erro

Uma chamada sozinha na linha, como Dobro(4);, compila e roda sem aviso nenhum. O método calcula o 8, devolve, e ninguém pega: o valor se perde.

$ dotnet run ignora.cs                « Dobro(4); sozinho na linha 1
O resultado se perdeu

É um engano comum: Dobrar(pontos); no lugar de pontos = Dobrar(pontos);. Se o método devolve algo, pergunte-se: "onde estou guardando isso?".


3. O return encerra o método

O return faz duas coisas ao mesmo tempo: entrega o valor e sai do método na hora. O que estiver escrito depois dele, no mesmo caminho, nunca roda. O compilador percebe e avisa:

Console.WriteLine(Dobro(4));

int Dobro(int n)
{
    return n * 2;
    Console.WriteLine("Calculei!");
}
$ dotnet run depoisreturn.cs
/home/ana/cs/depoisreturn.cs(6,5): warning CS0162: Código inacessível detectado
8

Código inacessível (Unreachable code detected): a linha 6 nunca vai rodar, porque o método sempre sai na linha 5. É só um aviso (o programa rodou e mostrou 8), mas é quase sempre um engano: troque a ordem das duas linhas, se queria a mensagem.

Vários return, um para cada caso

Como o return sai na hora, dá para usar um em cada ramo de um if. O primeiro que for alcançado decide a resposta:

Console.WriteLine(Situacao(8.5));
Console.WriteLine(Situacao(6.0));
Console.WriteLine(Situacao(4.5));

string Situacao(double media)
{
    if (media >= 7)
    {
        return "Aprovada";
    }
    if (media >= 5)
    {
        return "Recuperação";
    }
    return "Reprovada";
}
$ dotnet run situacao.cs
Aprovada
Recuperação
Reprovada
  1. Com 8,5: o primeiro if é verdadeiro, o método devolve "Aprovada" e sai. O resto nem é olhado.
  2. Com 6,0: o primeiro if é falso; o segundo é verdadeiro, e o método devolve "Recuperação".
  3. Com 4,5: os dois são falsos, e o programa chega no último return.

Repare que não precisou de else: se o primeiro return rodou, o método já saiu. O tipo de retorno é string, porque a resposta é um texto.

Todo caminho precisa de um return

Num método com tipo de retorno, o compilador confere todos os caminhos possíveis. Se existe um jeito de chegar no } final sem passar por um return, ele não aceita:

Console.WriteLine(Situacao(8.5));

string Situacao(double media)
{
    if (media >= 7)
    {
        return "Aprovada";
    }
}
$ dotnet run faltareturn.cs
/home/ana/cs/faltareturn.cs(3,8): error CS0161: "Situacao(double)": nem todos os caminhos de código retornam um valor

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

Nem todos os caminhos de código retornam um valor (not all code paths return a value). E se a média for 5? O if é falso, o método chega ao fim, e não tem resposta nenhuma para entregar. Repare que o programa chamou com 8,5, que daria certo: não importa, o compilador olha o método inteiro, para qualquer valor. Conserto: um return no fim (return "Reprovada";) ou um else com return.

Missão: o programa não compila (CS0161). Acrescente um return que devolva "Reprovada" quando a média for menor que 7, e rode.

Console.WriteLine(Situacao(8.5));
Console.WriteLine(Situacao(4.0));

string Situacao(double media)
{
    if (media >= 7)
    {
        return "Aprovada";
    }
}
Console.WriteLine(Situacao(8.5));
Console.WriteLine(Situacao(4.0));

string Situacao(double media)
{
    if (media >= 7)
    {
        return "Aprovada";
    }
    return "Reprovada";
}

return; num método void

Um método void não devolve nada, mas pode usar return; sozinho, sem valor, para sair mais cedo:

Saudar("");
Saudar("Ana");

void Saudar(string nome)
{
    if (nome == "")
    {
        Console.WriteLine("Sem nome: nada a fazer.");
        return;
    }
    Console.WriteLine($"Olá, {nome}!");
}
$ dotnet run retvoid.cs
Sem nome: nada a fazer.
Olá, Ana!

Na primeira chamada, o nome está vazio: o método avisa e o return; o encerra antes do Olá. Na segunda, o if é falso e o método segue até o fim. Esse "teste no começo e sai" é muito usado para recusar valores inválidos logo de cara.


4. O corpo curto com =>

Quando o método só tem um return com uma conta, o C# permite escrever o corpo numa linha, com uma seta => no lugar de { return …; }. Isso se chama corpo de expressão (expression body) C# 7:

Console.WriteLine(Dobro(7));
Console.WriteLine(EhPar(7));
Console.WriteLine(EhPar(10));
Console.WriteLine(Total(12.50m, 3).ToString("C"));

int Dobro(int n) => n * 2;
bool EhPar(int n) => n % 2 == 0;
decimal Total(decimal preco, int quantidade) => preco * quantidade;
$ dotnet run seta.cs
14
False
True
R$ 37,50
int Dobro(int n) => n * 2;
O mesmo que int Dobro(int n) { return n * 2; }. Leia a seta como "devolve": "Dobro de n devolve n vezes 2". Sem chaves, sem return, com ; no fim.
bool EhPar(int n)
Um método pode devolver bool: a comparação n % 2 == 0 vale true ou false. O Console.WriteLine mostra um bool como True ou False, com maiúscula. Métodos assim costumam começar com Eh, Tem, Pode (em inglês, Is, Has, Can) e ficam ótimos dentro de um if: if (EhPar(numero)).
Total(12.50m, 3).ToString("C")
O resultado é um decimal, e dá para chamar métodos nele direto, como em qualquer decimal.

A seta também serve para métodos void de uma linha só: void Linha() => Console.WriteLine("----------");. Use quando o método couber numa linha e ficar fácil de ler; se precisar de um if ou de variáveis, volte às chaves.


Formas diferentes

Tudo o que você viu funciona igual na forma com classe; só aparece o static antes do tipo de retorno:

class Program
{
    static void Main()
    {
        Console.WriteLine(Dobro(21));
    }

    static int Dobro(int n)
    {
        return n * 2;
    }
}
$ dotnet run classeret.cs
42

Repare no static void Main(): o Main também tem tipo de retorno, e é void. Ele pode ser static int Main(), e o número devolvido vira o código de saída do programa (o echo $? do terminal); veja Program.cs e o Main.

Qual usar?

SituaçãoForma
O método calcula algo que quem chamou vai usar (um total, uma média, uma resposta sim/não)Tipo de retorno + return
O método só faz uma ação (mostrar, desenhar) e não há resultadovoid
O corpo é um único return curto=>
Precisa devolver dois valores ao mesmo tempoUma tupla ((int, int)) ou parâmetros out
Prefira devolver a mostrar

Um método que devolve o total pode ser usado para mostrar, para somar, para salvar num arquivo. Um método que escreve o total na tela só serve para mostrar. Por isso, quando o método calcula algo, a regra do guia é: devolva o valor e deixe o Console.WriteLine para quem chamou.


Quando dá errado

error CS0161: "Situacao(double)": nem todos os caminhos de código retornam um valor

O que significa: existe algum caminho pelo método que chega ao } sem passar por um return. Acontece muito com if sem else, e com um return só dentro de um laço.

Como consertar: garanta um return no fim do método, ou um else com return. O compilador não sabe com quais valores você vai chamar; ele exige uma resposta para todos.

error CS0126: Um objeto de tipo conversível em "int" é necessário
$ dotnet run returnvazio.cs
/home/ana/cs/returnvazio.cs(6,5): error CS0126: Um objeto de tipo conversível em "int" é necessário

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

O que significa: num método int Dobro(int n), a linha 6 tem return; sem valor. O método prometeu um int e não entregou. (A palavra "objeto", aqui, quer dizer "valor".)

Como consertar: escreva o valor depois do return: return d;.

error CS0127: Como "Mostrar(int)" retorna void, uma palavra-chave return não deve ser seguida por uma expressão de objeto
$ dotnet run voidvalor.cs
/home/ana/cs/voidvalor.cs(6,5): error CS0127: Como "Mostrar(int)" retorna void, uma palavra-chave return não deve ser seguida por uma expressão de objeto

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

O que significa: o contrário do anterior: o método é void Mostrar(int n) e tem return n;. Um método void não devolve nada.

Como consertar: se o método deve devolver o número, troque void por int. Se não, deixe só return; (ou tire o return).

error CS0664: Literal do tipo double não pode ser convertido implicitamente no tipo "decimal"; use um sufixo "M"…
$ dotnet run tiporet.cs
/home/ana/cs/tiporet.cs(5,12): error CS0664: Literal do tipo double não pode ser convertido implicitamente no tipo "decimal"; use um sufixo "M" para criar um literal desse tipo

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

O que significa: o método é decimal Desconto() e a linha 5 é return 1.5;. O valor do return tem de ser do tipo de retorno, e 1.5 sem letra é double. Com um texto num método int (return "dez";), o erro é o CS0029: Não é possível converter implicitamente tipo "string" em "int".

Como consertar: devolva um valor do tipo certo: return 1.5m;, return 10;. Ou mude o tipo de retorno, se ele é que estava errado.

error CS0815: Não é possível atribuir void a uma variável de tipo implícito
$ dotnet run varvoid.cs
/home/ana/cs/varvoid.cs(1,5): error CS0815: Não é possível atribuir void a uma variável de tipo implícito

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

O que significa: var x = Linha();, com void Linha(). O var descobre o tipo pelo valor (Inferência com var), mas um método void não tem valor. Com int x no lugar de var x, o erro é o CS0029 que você viu em Métodos.

Como consertar: chame o método sozinho, ou faça-o devolver um valor.

warning CS0162: Código inacessível detectado

O que significa: há linhas depois de um return que nunca vão rodar.

Como consertar: mova essas linhas para antes do return, ou apague-as.


Referência rápida

EscritaO que faz
int Nome(…) { … return valor; }Método que devolve um int
return expressão;Calcula a expressão, entrega o valor e sai do método na hora
return;Sai de um método void sem devolver nada
var x = Nome(…);Guarda o valor devolvido
x = Nome(x);"Muda" a variável com o valor novo que o método devolveu
int Nome(int n) => n * 2;Corpo de expressão: o mesmo que { return n * 2; }
bool EhPar(int n) => n % 2 == 0;Método que responde sim/não, bom para if

Exercícios

1. O tipo de retorno

Complete a declaração de um método que devolve uma média com casas decimais (double):

Media(double a, double b)

2. Devolver

Complete o corpo do método para devolver a soma:

int Somar(int a, int b) { }

3. Quanto vale?

Com int Dobro(int n) => n * 2;, quanto vale Dobro(Dobro(3)) + 1?

4. O que acontece com uma linha escrita logo depois de um return, dentro do mesmo bloco?

5. A linha Dobrar(pontos); (com int Dobrar(int v) => v * 2;) sozinha no programa. O que acontece com pontos?

6. Por que o compilador recusa um método string que só tem return dentro de um if?

Missão final: o desconto da lojinha

Missão: a lojinha dá 10% de desconto nas compras de R$ 100,00 ou mais. O método Total já funciona. Complete ComDesconto: se valor for maior ou igual a 100, devolva valor * 0.9m; senão, devolva o próprio valor. A saída deve ser:

Total: R$ 135,00
Com desconto: R$ 121,50
Total: R$ 25,00
Com desconto: R$ 25,00
decimal grande = Total(45.00m, 3);
Console.WriteLine($"Total: {grande:C}");
Console.WriteLine($"Com desconto: {ComDesconto(grande):C}");

decimal pequena = Total(12.50m, 2);
Console.WriteLine($"Total: {pequena:C}");
Console.WriteLine($"Com desconto: {ComDesconto(pequena):C}");

decimal Total(decimal preco, int quantidade) => preco * quantidade;

decimal ComDesconto(decimal valor)
{
    // complete aqui
    return valor;
}
decimal grande = Total(45.00m, 3);
Console.WriteLine($"Total: {grande:C}");
Console.WriteLine($"Com desconto: {ComDesconto(grande):C}");

decimal pequena = Total(12.50m, 2);
Console.WriteLine($"Total: {pequena:C}");
Console.WriteLine($"Com desconto: {ComDesconto(pequena):C}");

decimal Total(decimal preco, int quantidade) => preco * quantidade;

decimal ComDesconto(decimal valor)
{
    if (valor >= 100)
    {
        return valor * 0.9m;
    }
    return valor;
}

Se apareceu CS0019: O operador "*" não pode ser aplicado a operandos dos tipos "decimal" e "double", faltou o m do 0.9m: um decimal não se mistura com double.


Resumo

  • Para o método devolver um valor, troque o void pelo tipo do resultado e use return valor;.
  • Uma chamada com retorno vale o resultado: guarde numa variável, use numa conta, passe para outro método. Se ninguém guardar, o valor se perde, sem aviso.
  • O return sai do método na hora. Linhas depois dele dão o aviso CS0162. Vários return com if são comuns; todo caminho precisa de um (CS0161).
  • return; sozinho sai cedo de um método void. Num método com tipo, falta o valor (CS0126); num void, sobra (CS0127).
  • O valor devolvido tem de ser do tipo de retorno (CS0029, CS0664). Métodos curtos podem usar =>.