Basicamente, existem dois tipos de polimorfismo em C#, o de sobreposição e o de sobrecarga.
O de sobreposição, visto anteriormente, é quando um método, indicado como virtual, é substituído em uma classe descendente, onde ele tem o mesmo nome e é indicado por override.
E como visto nos construtores e nos campos compartilhados, podemos criar métodos comuns usando o mesmo nome, desde que os parâmetros sejam diferentes, isso é o polimorfismo de sobrecarga, como nesse exemplo:
class Microondas {
public void ligar(int minuto, int segundo) {
if(minuto > 0 || segundo > 0) {
Console.WriteLine("A comida estará pronta em {0}:{1:00}M.", minuto, segundo);
}
else {
Console.WriteLine("Escolha um tempo maior que 0:00.");
}
}
public void ligar(string alimento) {
if(alimento.ToLower().Equals("pipoca")) {
Console.WriteLine("Vamos estourar pipoca!");
}
else if(alimento.ToLower().Equals("bolo de caneca")) {
Console.WriteLine("Vamos fazer bolo de caneca!");
}
else {
Console.WriteLine("ERRO! Alimento desconhecido!");
}
}
public void ligar(bool descongelar) {
if(descongelar) {
Console.WriteLine("Vamos descongelar o alimento!");
}
else {
Console.WriteLine("ERRO! Não poderei descongelar o alimento!");
}
}
Na invocação no método principal, ele será diferenciado pelo tipo de parâmetro:
Microondas micro = new Microondas();
micro.ligar(3, 30);
micro.ligar("Pipoca");
micro.ligar(true);
Vamos supor essa classe:
class Pilha {
private string marca;
private int carga;
public Pilha(string marca) {
this.marca = marca;
this.carga = 100;
}
public void apresentacao() {
Console.WriteLine($"A marca da pilha é {this.marca}.");
Console.WriteLine($"A carga da pilha é {this.carga}%.");
}
public string Marca {
get => marca;
set => marca = value;
}
public int Carga {
get => carga;
set => carga = value;
}
}
Nós podemos fazer relacionamentos entre classes diferentes, veja por exemplo a classe abaixo, que tem um atributo do "tipo" da classe acima:
class Aparelho {
private Pilha pl;
public Aparelho(Pilha pl) {
this.pl = pl;
}
public void ligado() {
if(this.pl.Carga > 0) { // Getter do objeto Pilha
Console.WriteLine($"O aparelho está ligado e a carga da pilha é de {this.pl.Carga}%!");
}
else {
Console.WriteLine($"A pilha do aparelho está sem carga!");
}
}
public Pilha Pl {
get => pl;
set => pl = value;
}
}
Aí podemos chamar os objetos assim:
Pilha ray = new Pilha("Rayovac");
ray.apresentacao();
Aparelho controle = new Aparelho(ray);
controle.ligado();
Console.WriteLine($"A carga da pilha é de {controle.Pl.Carga}%!");
PS: Quando a gente inicia um objeto criado dentro da classe, na verdade se chama composição. No caso acima, caso a classe Aparelho fizesse a composição de Pilha, ela teria algo como private Pilha pl = new Pilha("Rayovac"); dentro da classe Aparelho.
Podemos criar também "tipos genéricos" em C#, usando apenas uma letra maiúscula, que nos permite configurar os tipos usados.
Veja um exemplo simples de uso:
class Program {
private class Generico<E> { // Classe interna privada, com parâmetro de tipagem.
private E entidade;
public E Entidade {
get => this.entidade;
set => this.entidade = value;
}
}
static void Main(string[] args) {
Generico<string> texto = new Generico<string>();
texto.Entidade = "Exemplo de String!";
Console.WriteLine(texto.Entidade);
Generico<int> numero = new Generico<int>();
numero.Entidade = 50;
Console.WriteLine(numero.Entidade);
Console.Read();
}
}
PS: Podemos criar normalmente uma classe externa com tipos genéricos, basta adaptar o código acima.
Caso use mais de um tipo genérico, coloque dentro da mesma tag, separados por vírgulas. Em heranças e implementações, caso a classe a ser herdada ou a interface a ser implementada tenha tipos genéricos, a classe herdeira ou implementadora deverá ter o mesmo tipo (por exemplo, se a interface for interface NomeDaInterface<E>, a classe que a implementa deverá ter o tipo a ser recebido indicado, como por exemplo class NomeDaClasse : NomeDaInterface<Int32>, e nesse caso o objeto criado será NomeDaInterface<Int32> objeto = new NomeDaClasse(). Caso não declare o tipo genérico e queira que ele seja declarado na criação do objeto, coloque a extensão ou implementação (no caso seria class NomeDaClasse<E> : NomeDaInterface<E>), e nesse caso o objeto criado será NomeDaInterface<ClasseInvolucro> objeto = new NomeDaClasse<ClasseInvolucro>(). Esse é o conceito de polimorfismo paramétrico.