C# Sobrecarga

O Console.WriteLine aceita um número, um texto, um bool, nada… Como um método só aceita tantos tipos diferentes? Ele não é um método só: são vários métodos com o mesmo nome, cada um com uma lista de parâmetros. Isso se chama sobrecarga (overload), e o compilador escolhe a versão certa olhando os argumentos de cada chamada. Nesta aula você vai escrever as suas sobrecargas, ver o que torna duas versões diferentes e descobrir uma pegadinha: as funções locais do fim do arquivo não podem ser sobrecarregadas.

Você vai aprender
  • O que é sobrecarga e onde você já usou uma.
  • Por que, para sobrecarregar, os métodos precisam morar numa classe (e não ser funções locais).
  • Sobrecarga pelo tipo e pela quantidade de parâmetros.
  • O que conta na assinatura do método, e o que não conta (o tipo de retorno e os nomes dos parâmetros).
  • Como o compilador escolhe a versão, e quando ele não consegue escolher.
  • Quando usar sobrecarga, parâmetros opcionais ou params.
  • Os erros CS0128, CS0111, CS0121 e CS1503.

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: uma palavra, vários sentidos

Em português, "abrir" serve para muita coisa: abrir a porta, abrir uma conta, abrir o guarda-chuva. Ninguém se confunde, porque o complemento diz qual "abrir" é: com uma chave, é a porta; com documentos, é a conta.

A sobrecarga é isso. Os métodos têm o mesmo nome, e o que vai entre os parênteses decide qual deles roda:

Console.WriteLine(42);
Console.WriteLine(3.5);
Console.WriteLine(true);
Console.WriteLine('A');
Console.WriteLine("texto");
Console.WriteLine();
Console.WriteLine("{0} + {1} = {2}", 2, 3, 5);
int quantas = 0;
foreach (var m in typeof(Console).GetMethods())
    if (m.Name == "WriteLine") quantas++;
Console.WriteLine($"Console.WriteLine tem {quantas} sobrecargas");
$ dotnet run writelineover.cs
42
3,5
True
A
texto

2 + 3 = 5
Console.WriteLine tem 20 sobrecargas

As últimas três linhas do programa contam quantos métodos chamados WriteLine a classe Console tem (é um truque avançado; você não precisa entendê-lo agora). São 20: um para int, um para double, um para bool, um para char, um para string, um sem nada, um com texto e valores para encaixar… Você sempre escreveu só Console.WriteLine, e o compilador sempre escolheu.


1. A pegadinha: funções locais não se sobrecarregam

A primeira tentativa natural é escrever as duas versões no fim do arquivo, como nas aulas anteriores:

Console.WriteLine(Dobro(4));
Console.WriteLine(Dobro(2.5));

int Dobro(int n) => n * 2;
double Dobro(double n) => n * 2;
$ dotnet run sobrelocal.cs
/home/ana/cs/sobrelocal.cs(2,25): error CS1503: Argumento 1: não é possível converter de "double" para "int"
/home/ana/cs/sobrelocal.cs(5,8): error CS0128: Uma variável de local ou função denominada 'Dobro' já está definida neste escopo
/home/ana/cs/sobrelocal.cs(5,8): warning CS8321: A função local 'Dobro' está declarada, mas nunca é usada

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

Lembra de Métodos? Num arquivo com instruções de nível superior, os métodos do fim do arquivo são funções locais do Main escondido. E uma função local se comporta como uma variável: dentro de um bloco, cada nome só pode existir uma vez. O CS0128 é o mesmo erro de declarar duas variáveis int x. Os outros dois são consequência: o compilador só ficou com o primeiro Dobro (o de int), por isso Dobro(2.5) deu CS1503, e o segundo ficou "sem uso".

Regra: sobrecarga é coisa de método de classe

Para ter duas versões com o mesmo nome, os métodos precisam morar numa classe, na forma com static que você viu em Métodos. É a forma que esta aula usa daqui para a frente. Se você prefere ficar com as funções locais, dê nomes diferentes (DobroInt, DobroDouble) ou use um parâmetro opcional.


2. Sobrecarga pelo tipo

class Program
{
    static void Main()
    {
        Console.WriteLine(Dobro(4));
        Console.WriteLine(Dobro(2.5));
        Console.WriteLine(Dobro("la"));
    }

    static int Dobro(int n)
    {
        return n * 2;
    }

    static double Dobro(double n)
    {
        return n * 2;
    }

    static string Dobro(string texto)
    {
        return texto + texto;
    }
}
$ dotnet run sobre1.cs
8
5
lala
static int Dobro(int n)
A versão para inteiros. Dobro(4) casa com ela: o argumento é int.
static double Dobro(double n)
A versão para números com vírgula. Dobro(2.5) casa com ela, e devolve 5,0 (que o WriteLine mostra como 5).
static string Dobro(string texto)
Uma versão que faz outra coisa: repete o texto. O mesmo nome, porque a ideia é a mesma ("dobro"), mas o trabalho depende do tipo.

Os três métodos moram dentro de class Program, ao lado do Main, como irmãos. Para o compilador, eles são três métodos diferentes que por acaso têm o mesmo nome.

3. Sobrecarga pela quantidade

As versões também podem ter quantidades diferentes de parâmetros. A área de um quadrado precisa de um lado; a de um retângulo, de dois:

class Program
{
    static void Main()
    {
        Console.WriteLine(Area(3));
        Console.WriteLine(Area(3, 5));
    }

    static double Area(double lado)
    {
        return lado * lado;
    }

    static double Area(double largura, double altura)
    {
        return largura * altura;
    }
}
$ dotnet run area.cs
9
15

Area(3) tem um argumento: só a primeira versão serve. Area(3, 5) tem dois: só a segunda. Repare que os argumentos são int e os parâmetros, double: tudo bem, um int vira double sozinho (Conversão de Tipos).

Uma versão pode chamar a outra

Quando a versão curta é só "a longa com um valor fixo", não repita o código: faça a curta chamar a longa.

class Program
{
    static void Main()
    {
        MostrarPreco(12.5m);
        MostrarPreco("Caderno", 12.5m);
    }

    static void MostrarPreco(decimal preco)
    {
        MostrarPreco("Produto", preco);
    }

    static void MostrarPreco(string produto, decimal preco)
    {
        Console.WriteLine($"{produto}: {preco:C}");
    }
}
$ dotnet run encadear.cs
Produto: R$ 12,50
Caderno: R$ 12,50

O formato da linha está escrito num lugar só. Se a Ana mudar o formato, as duas versões mudam juntas.

Missão: a segunda chamada passa três números, e o programa não compila (CS1501). Escreva, dentro da classe, uma segunda versão de Somar, com três parâmetros int. A saída deve ser:

5
9
class Program
{
    static void Main()
    {
        Console.WriteLine(Somar(2, 3));
        Console.WriteLine(Somar(2, 3, 4));
    }

    static int Somar(int a, int b)
    {
        return a + b;
    }
}
class Program
{
    static void Main()
    {
        Console.WriteLine(Somar(2, 3));
        Console.WriteLine(Somar(2, 3, 4));
    }

    static int Somar(int a, int b)
    {
        return a + b;
    }

    static int Somar(int a, int b, int c)
    {
        return a + b + c;
    }
}

4. O que conta na assinatura

O compilador diferencia as versões pela assinatura: o nome do método e a lista de tipos dos parâmetros (quantos são, de que tipo, em que ordem, e se têm ref, out ou in). Duas coisas não entram na assinatura:

O tipo de retorno não conta

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

    static int Ler() => 1;
    static string Ler() => "um";
}
$ dotnet run soretorno.cs
/home/ana/cs/soretorno.cs(9,19): error CS0111: Tipo "Program" já define um membro chamado "Ler" com os mesmos tipos de parâmetro

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

Na chamada Ler() não há nada que diga qual das duas você quer: o compilador escolhe pelos argumentos, nunca pelo que você vai fazer com o resultado. As duas têm a mesma lista de parâmetros (vazia), e isso é proibido. Em inglês: Type 'Program' already defines a member called 'Ler' with the same parameter types.

Os nomes dos parâmetros não contam

class Program
{
    static void Main()
    {
        Console.WriteLine(Area(3, 5));
    }

    static double Area(double largura, double altura) => largura * altura;
    static double Area(double base_, double h) => base_ * h / 2;
}
$ dotnet run sonome.cs
/home/ana/cs/sonome.cs(9,19): error CS0111: Tipo "Program" já define um membro chamado "Area" com os mesmos tipos de parâmetro

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

A segunda versão queria ser a área de um triângulo, mas as duas são (double, double). A solução aqui é um nome melhor: AreaRetangulo e AreaTriangulo. Sobrecarga serve para a mesma ideia com entradas diferentes; ideias diferentes merecem nomes diferentes.

Diferença entre as versõesPode sobrecarregar?Exemplo
Quantidade de parâmetrosSimArea(double) × Area(double, double)
Tipo de algum parâmetroSimDobro(int) × Dobro(double)
Ordem dos tiposSim (mas cuidado, seção 5)M(int, double) × M(double, int)
ref/out/in num parâmetroSimMudar(int) × Mudar(ref int)
Só o tipo de retornoNão (CS0111)int Ler() × string Ler()
Só os nomes dos parâmetrosNão (CS0111)Area(double largura, …) × Area(double base_, …)

5. Como o compilador escolhe

Em cada chamada, o compilador olha os tipos dos argumentos e procura a versão que combina melhor. Se houver uma com os tipos exatos, é ela. Se não, ele procura uma em que os argumentos caibam com uma conversão implícita.

class Program
{
    static void Main()
    {
        Mostrar(5);
        Mostrar(5.0);
        Mostrar(5m);
        Mostrar('5');
    }

    static void Mostrar(int n) => Console.WriteLine($"int: {n}");
    static void Mostrar(double n) => Console.WriteLine($"double: {n}");
    static void Mostrar(decimal n) => Console.WriteLine($"decimal: {n}");
}
$ dotnet run escolha.cs
int: 5
double: 5
decimal: 5
int: 53                              « o caractere '5' virou o número 53

As três primeiras são exatas: 5 é int, 5.0 é double, 5m é decimal. A quarta é uma surpresa: não existe versão para char, e um char cabe num int (cada caractere tem um número; o do '5' é 53, veja char e bool). O compilador escolheu a versão int sem avisar.

Quando ele não consegue escolher

Às vezes duas versões servem igualmente bem, e nenhuma é melhor que a outra. O compilador se recusa a adivinhar:

class Program
{
    static void Main()
    {
        Juntar(1, 2);
    }

    static void Juntar(int a, double b) => Console.WriteLine("int, double");
    static void Juntar(double a, int b) => Console.WriteLine("double, int");
}
$ dotnet run ambiguo.cs
/home/ana/cs/ambiguo.cs(5,9): error CS0121: A chamada é ambígua entre os seguintes métodos ou propriedades: "Program.Juntar(int, double)" e "Program.Juntar(double, int)"

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

Juntar(1, 2) tem dois int. Na primeira versão, o segundo precisa virar double; na segunda, o primeiro. Um empate. A chamada é ambígua (The call is ambiguous): o compilador mostra as duas candidatas. Conserto na chamada: deixe o tipo claro (Juntar(1, 2.0)). Conserto melhor: não crie sobrecargas que só trocam a ordem dos tipos.

O mesmo acontece no escolha.cs com um long: Mostrar(5L) não cabe no int, mas cabe no double e no decimal, e nenhum dos dois é melhor.

$ dotnet run longo.cs                « Mostrar(5L) com as mesmas três versões
/home/ana/cs/longo.cs(5,9): error CS0121: A chamada é ambígua entre os seguintes métodos ou propriedades: "Program.Mostrar(double)" e "Program.Mostrar(decimal)"

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

Formas diferentes

Sobrecarga num arquivo com instruções de nível superior

Se você quer continuar com as instruções soltas no topo, pode pôr as sobrecargas numa classe separada, no fim do arquivo, e chamar com o nome da classe na frente:

Console.WriteLine(Calc.Dobro(4));
Console.WriteLine(Calc.Dobro(2.5));

static class Calc
{
    public static int Dobro(int n) => n * 2;
    public static double Dobro(double n) => n * 2;
}
$ dotnet run topclasse.cs
8
5

É assim que funcionam Math.Max e Console.WriteLine: Math e Console são classes, e os métodos são chamados com o nome delas na frente. O public ("público") deixa o método ser usado de fora da classe; ele é explicado em public, private e internal, e a static class, em static.

Sobrecarga, opcionais ou params?

As três ferramentas deixam "o mesmo método" aceitar chamadas diferentes. Cada uma tem o seu caso:

SituaçãoUse
Uma versão é a outra com um valor padrão (quantidade = 1)Parâmetro opcional: um método só
Quantidade variável de valores do mesmo tipoparams
Tipos diferentes pedem trabalhos diferentes (int × string)Sobrecarga
Quantidades diferentes com significados diferentes (quadrado × retângulo)Sobrecarga
Funções locais no fim do arquivoOpcionais, params ou nomes diferentes (sobrecarga não é possível)

Quando dá errado

error CS0128: Uma variável de local ou função denominada 'Dobro' já está definida neste escopo

O que significa: você tentou sobrecarregar funções locais (os métodos do fim de um arquivo com instruções de nível superior). Funções locais não podem repetir o nome.

Como consertar: leve os métodos para uma classe (a forma com static), use nomes diferentes, ou troque as duas versões por uma com parâmetro opcional.

error CS0111: Tipo "Program" já define um membro chamado "Ler" com os mesmos tipos de parâmetro

O que significa: duas versões têm a mesma lista de tipos de parâmetros. Elas só mudam no tipo de retorno ou nos nomes dos parâmetros, e isso não conta.

Como consertar: se as duas fazem coisas diferentes, dê nomes diferentes (LerNumero, LerTexto). Se fazem a mesma coisa, apague uma.

error CS0121: A chamada é ambígua entre os seguintes métodos ou propriedades…

O que significa: duas versões servem para os argumentos da chamada, e nenhuma é melhor.

Como consertar: deixe o tipo do argumento claro (2.0, 2m, (double)x), ou repense as sobrecargas para não haver empate.

error CS1503: Argumento 1: não é possível converter de "string" para "double"
$ dotnet run nenhuma.cs              « Area("3") com Area(double) e Area(double, double)
/home/ana/cs/nenhuma.cs(5,32): error CS1503: Argumento 1: não é possível converter de "string" para "double"

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

O que significa: nenhuma versão aceita os argumentos. O compilador mostra o erro comparando com a versão que tem a mesma quantidade de parâmetros. Com uma quantidade que nenhuma versão tem, o erro é o CS1501.

Como consertar: passe o tipo certo (Area(3)), converta antes (double.TryParse) ou crie a versão que falta.


Referência rápida

TermoO que é
Sobrecarga (overload)Vários métodos com o mesmo nome e listas de parâmetros diferentes
AssinaturaNome + tipos dos parâmetros (quantidade, tipo, ordem, ref/out/in)
Não entram na assinaturaTipo de retorno e nomes dos parâmetros
Escolha da versãoPelos tipos dos argumentos: exata primeiro, depois a que aceita com conversão implícita
Onde é possívelMétodos de classe (static ao lado do Main, ou numa static class). Funções locais, não

Exercícios

1. O nome técnico

Como se chama ter vários métodos com o mesmo nome e parâmetros diferentes? (uma palavra)

2. Qual roda?

Com static string Tipo(int n) => "int"; e static string Tipo(double n) => "double";, o que Tipo(7) devolve?

3. E agora?

Com as mesmas duas versões, o que Tipo(7.0) devolve?

4. Qual par de métodos, numa classe, dá CS0111?

5. Num arquivo com instruções de nível superior, a Ana escreve no fim int Dobro(int n) e double Dobro(double n). O que acontece?

6. Com Mostrar(int) e Mostrar(double), o que acontece em Mostrar('A')?

Missão final: as etiquetas da lojinha

Missão: escreva, dentro da classe, três versões de static string Etiqueta, que devolvem o texto da etiqueta:

  • Etiqueta(string produto): [Caderno];
  • Etiqueta(string produto, decimal preco): [Caderno - R$ 12,50] (preço com :C);
  • Etiqueta(string produto, int quantidade): [Caneta x3].

O Main já está pronto. A saída deve ser:

[Caderno]
[Caderno - R$ 12,50]
[Caneta x3]
class Program
{
    static void Main()
    {
        Console.WriteLine(Etiqueta("Caderno"));
        Console.WriteLine(Etiqueta("Caderno", 12.50m));
        Console.WriteLine(Etiqueta("Caneta", 3));
    }

    // escreva aqui as três versões de Etiqueta
}
class Program
{
    static void Main()
    {
        Console.WriteLine(Etiqueta("Caderno"));
        Console.WriteLine(Etiqueta("Caderno", 12.50m));
        Console.WriteLine(Etiqueta("Caneta", 3));
    }

    static string Etiqueta(string produto)
    {
        return $"[{produto}]";
    }

    static string Etiqueta(string produto, decimal preco)
    {
        return $"[{produto} - {preco:C}]";
    }

    static string Etiqueta(string produto, int quantidade)
    {
        return $"[{produto} x{quantidade}]";
    }
}

Repare que Etiqueta("Caneta", 3) escolheu a versão int, e não a decimal, embora um int também caiba num decimal: a versão exata vence.


Resumo

  • Sobrecarga: vários métodos com o mesmo nome e listas de parâmetros diferentes. O compilador escolhe a versão pelos argumentos de cada chamada.
  • Só métodos de classe podem ser sobrecarregados. As funções locais do fim do arquivo, não (CS0128).
  • A assinatura é o nome mais os tipos dos parâmetros. O tipo de retorno e os nomes dos parâmetros não contam (CS0111).
  • A versão exata vence; senão, vale a que aceita com conversão implícita (um char pode ir parar no int). Empate dá CS0121.
  • Use sobrecarga para a mesma ideia com entradas diferentes. Para um valor padrão, prefira um parâmetro opcional; para "quantos vierem", params.