1. Introdução
Na vida real, várias ações são tipo canivete suíço: o mesmo comando pode funcionar com diferentes ferramentas. Por exemplo, imagina um caixa eletrônico: se você coloca o cartão — ele pede o PIN; se digita o número do celular — ele espera o código do SMS. A ação é a mesma — "verificar usuário", mas os jeitos são diferentes.
Na programação, a gente tromba direto com isso: precisa fazer uma operação só, mas os dados podem ser de tipos diferentes ou com quantidades diferentes de parâmetros. Tipo, nosso método tem que mostrar uma saudação tanto pra uma pessoa quanto pra um animal, ou somar dois, três ou até dez números inteiros.
Claro, dá pra dar nomes diferentes pros métodos: SumTwo, SumThree, SumArray. Mas programador é preguiçoso (não é à toa que dizem que preguiça move o progresso). Fora que o código fica menos legível.
Sobrecarga de método
Sobrecarga de métodos é um jeito de "fazer" o mesmo método funcionar com diferentes conjuntos de parâmetros, sem mudar o nome do método. Isso é uma forma de polimorfismo, mas não tem a ver com herança.
Sobrecarga de método é a possibilidade de criar numa mesma classe (ou struct) vários métodos com o mesmo nome, mas com listas de parâmetros diferentes (tipo, quantidade e/ou ordem).
Assinatura do método
A assinatura do método em C# é o nome dele mais o(s) tipo(s) e ordem dos parâmetros. O tipo de retorno do método não faz parte da assinatura! Isso costuma causar uns bugs chatos (vou falar disso já já).
2. Sobrecarga na prática: exemplos simples
Bora criar uma classe Greeter que vai cumprimentar usuários de jeitos diferentes: só pelo nome, pelo nome e idade, ou até sem parâmetro nenhum.
public class Greeter
{
// Saudação sem parâmetros
public void Greet()
{
Console.WriteLine("Oi, mundo!");
}
// Saudação com nome
public void Greet(string name)
{
Console.WriteLine($"Oi, {name}!");
}
// Saudação com nome e idade
public void Greet(string name, int age)
{
Console.WriteLine($"Oi, {name}! Já tem {age} anos? Daora!");
}
}
Agora dá pra chamar qualquer um desses métodos, e o compilador C# escolhe sozinho a versão certa — olhando a quantidade e os tipos dos parâmetros que você passou.
var greeter = new Greeter();
greeter.Greet(); // Oi, mundo!
greeter.Greet("Anya"); // Oi, Anya!
greeter.Greet("Pyotr", 23); // Oi, Pyotr! Já tem 23 anos? Daora!
3. Diferença pelo tipo e quantidade de parâmetros
A sobrecarga rola se os métodos diferem por:
- quantidade de parâmetros,
- tipo de pelo menos um parâmetro,
- ordem dos tipos dos parâmetros (mas aqui tem que tomar cuidado).
Vamos tentar adicionar mais uma sobrecarga, que recebe só a idade:
public void Greet(int age)
{
Console.WriteLine($"Essa idade é massa! ({age} anos)");
}
Agora as chamadas:
greeter.Greet(10); // Essa idade é massa! (10 anos)
Importante lembrar: se os métodos diferem só pelo tipo de retorno, não dá pra sobrecarregar não rola. Tipo, esse código vai dar erro:
// Erro de compilação!
public int Foo(string s) { ... }
public double Foo(string s) { ... }
O compilador vai reclamar: "Já tem método Foo(string), faz algo mais difícil aí!"
4. Sobrecarga e a biblioteca padrão do C#
Sobrecarga não é só nosso Greet inventado. Bora olhar a documentação do .NET pra Console.WriteLine:
| Assinatura | Pra que serve |
|---|---|
|
Imprime uma linha vazia |
|
Imprime uma string |
|
Imprime um número inteiro |
|
Imprime um número decimal |
|
Formata uma string com um argumento |
|
Formata com vários argumentos |
Tudo isso são sobrecargas do mesmo método — WriteLine. Agora você entende porque sempre dá pra fazer:
Console.WriteLine("Só uma string");
Console.WriteLine(123);
Console.WriteLine(2.5);
Console.WriteLine("Soma: {0}", 42);
E o compilador sempre entende direitinho sua chamada!
5. Como o compilador escolhe qual sobrecarga chamar?
Aqui é na régua: ele olha os tipos e a quantidade dos argumentos que você passou. Uma tabelinha pra clarear:
| Chamada | Qual versão vai rodar? |
|---|---|
|
|
|
|
E se rolar ambiguidade?
Às vezes a coisa sai do controle. Exemplo de sobrecarga ambígua — o compilador não consegue escolher a versão certa
public void Print(int a, double b) { ... }
public void Print(double a, int b) { ... }
printer.Print(5, 10);
// Erro: ambiguidade — qual Print chamar? (os dois servem)
O compilador vai dar erro de ambiguidade. Nesses casos, melhor evitar sobrecargas com a mesma quantidade de parâmetros e tipos parecidos, porque isso confunde o compilador.
6. params — quantidade variável de parâmetros
Digamos que você quer um método que aceita uma quantidade indefinida de números. Aí entra a palavra-chave params.
public void SumAll(params int[] numbers)
{
int sum = 0;
foreach (int n in numbers)
sum += n;
Console.WriteLine($"Soma: {sum}");
}
Agora você pode chamar:
SumAll(1, 2, 3); // Soma: 6
SumAll(10, 20); // Soma: 30
SumAll(); // Soma: 0
Métodos com params podem ser combinados com sobrecarga, mas o principal é não criar sobrecargas que deixem o compilador confuso sobre qual versão você quis chamar.
7. Sobrecarga e modificadores de parâmetro (ref, out, in)
C# diferencia métodos pelos modificadores de parâmetro (ou seja, a assinatura void Foo(int a) é diferente de void Foo(ref int a), e os dois podem existir na mesma classe):
public void SetValue(int a)
{
a = 42;
}
public void SetValue(ref int a)
{
a = 100;
}
Chamar sem ref vai pra primeira versão, com ref — pra segunda:
int n = 5;
SetValue(n); // n continua 5 (valor copiado)
SetValue(ref n); // n vira 100
8. Esquema: o que é sobrecarga
+----------+
| MyClass |
+----------+
|
| (trecho de métodos)
+-----------------------+
| void Foo() |
| void Foo(int a) |
| void Foo(string s) |
| void Foo(int a, int b) |
+-----------------------+
E se for no código:
// Chamando as versões sobrecarregadas do método Foo():
var mc = new MyClass();
mc.Foo(); // void Foo()
mc.Foo(5); // void Foo(int)
mc.Foo("Hello"); // void Foo(string)
mc.Foo(2, 3); // void Foo(int, int)
9. Exemplo: sobrecarregando métodos no nosso app
Bora continuar nosso app de estudo, adicionando sobrecarga de método na hierarquia dos animais.
public class Animal
{
public string Name { get; set; }
// Método pra fazer barulho
public virtual void MakeSound()
{
Console.WriteLine("Um som meio estranho...");
}
// Método sobrecarregado: som com volume definido
public void MakeSound(int volume)
{
Console.WriteLine($"O animal faz um som com volume {volume} dB.");
}
}
public class Dog : Animal
{
public override void MakeSound()
{
Console.WriteLine("Au au!");
}
// Método sobrecarregado: latido com volume
public void MakeSound(int volume)
{
Console.WriteLine($"Au au! (volume: {volume} dB)");
}
}
Tenta essas chamadas:
Dog rex = new Dog();
rex.MakeSound(); // Au au!
rex.MakeSound(75); // Au au! (volume: 75 dB)
Repara: na classe filha (Dog) a gente sobrecarregou o método MakeSound(int volume), e agora tem as duas versões: com e sem parâmetro.
10. Erros comuns na sobrecarga de métodos
Erro #1: tentar sobrecarregar só pelo tipo de retorno.
Não rola — tipo de retorno não faz parte da assinatura do método. Sobrecarga tem que ser diferente na quantidade ou tipos dos parâmetros de entrada, não pelo void ou int.
Erro #2: sobrecargas ambíguas que confundem o compilador.
Sobrecargas com o mesmo número de parâmetros e tipos parecidos (tipo int e double) podem deixar o compilador perdido. Exemplo: Print(int a, double b) e Print(double a, int b) — chamar Print(1, 1) vai dar erro de ambiguidade.
Erro #3: conflito de params com outras sobrecargas.
Método com params pode capturar uma chamada que era pra outra sobrecarga. Se os tipos batem, o compilador pode escolher o método errado sem você perceber.
Erro #4: esquecer que ref e out fazem parte da assinatura.
Métodos Do(ref int x) e Do(out int x) são sobrecargas diferentes. Se não lembrar disso, é fácil se confundir e chamar a versão errada do método.
GO TO FULL VERSION