C# Herança

Um cachorro e um gato são diferentes, mas têm muita coisa em comum: os dois têm nome, os dois comem, os dois dormem. Escrever tudo isso duas vezes é trabalho dobrado e erro dobrado. Com herança, você escreve o que é comum uma vez, numa classe Animal, e as classes Cachorro e Gato recebem tudo de presente, acrescentando só o que é delas. É o segundo pilar da orientação a objetos.

Você vai aprender
  • Que problema a herança resolve (o código repetido).
  • A sintaxe: class Cachorro : Animal.
  • Os nomes: classe base e classe derivada.
  • O teste do "é um", para saber quando usar herança.
  • O que é herdado e o que não é (o private e os construtores).
  • Herança em vários níveis, a regra de uma classe base só e o object no topo de tudo.
  • Os erros CS1061, CS0122, CS1721, CS7036 e CS1729.

A ideia: herdar características

A Ana herdou da mãe a cor dos olhos e do pai o sobrenome. Ela não precisou "criar" essas coisas: nasceu com elas. Mas a Ana também tem coisas só dela, como o gosto por violão.

Em C#, uma classe pode nascer com tudo o que outra classe tem e acrescentar o que é só dela:

  • A classe que dá as características é a classe base (também chamada de classe mãe, ou superclasse): Animal.
  • A classe que recebe é a classe derivada (classe filha, ou subclasse): Cachorro, Gato.

O problema: código repetido

A Ana está fazendo um joguinho de bichos de estimação. Primeira versão, sem herança:

var rex = new Cachorro { Nome = "Rex" };
var mia = new Gato { Nome = "Mia" };
rex.Comer();
rex.Latir();
mia.Comer();
mia.Miar();

class Cachorro
{
    public string Nome { get; set; } = "";

    public void Comer()
    {
        Console.WriteLine($"{Nome} está comendo.");
    }

    public void Latir()
    {
        Console.WriteLine($"{Nome}: Au au!");
    }
}

class Gato
{
    public string Nome { get; set; } = "";

    public void Comer()
    {
        Console.WriteLine($"{Nome} está comendo.");
    }

    public void Miar()
    {
        Console.WriteLine($"{Nome}: Miau!");
    }
}
$ dotnet run repetido.cs
Rex está comendo.
Rex: Au au!
Mia está comendo.
Mia: Miau!

Funciona, mas Nome e Comer estão escritos duas vezes, letra por letra. Se a Ana quiser mudar a frase para "está comendo ração", precisa lembrar de mudar nos dois lugares. Com um coelho, um peixe e um papagaio, seriam cinco cópias.


: Animal, herdando da classe base

var rex = new Cachorro { Nome = "Rex" };
var mia = new Gato { Nome = "Mia" };
rex.Comer();
rex.Latir();
mia.Comer();
mia.Miar();

class Animal
{
    public string Nome { get; set; } = "";

    public void Comer()
    {
        Console.WriteLine($"{Nome} está comendo.");
    }
}

class Cachorro : Animal
{
    public void Latir()
    {
        Console.WriteLine($"{Nome}: Au au!");
    }
}

class Gato : Animal
{
    public void Miar()
    {
        Console.WriteLine($"{Nome}: Miau!");
    }
}
$ dotnet run heranca.cs
Rex está comendo.
Rex: Au au!
Mia está comendo.
Mia: Miau!          « a mesma saída, sem nada repetido
class Animal
A classe base. Tem o que todo animal tem: Nome e Comer(). Ela é uma classe comum; nada nela diz que alguém vai herdar.
class Cachorro : Animal
Os dois-pontos se leem "herda de". Cachorro nasce com Nome e Comer(), sem escrever uma linha deles.
public void Latir()
O que é só do cachorro. A classe derivada acrescenta coisas à base.
{Nome} dentro de Latir
A classe derivada usa o que herdou como se fosse dela.
rex.Comer()
Quem usa o objeto também não vê diferença: Comer está lá, herdado.

Agora, mudar a frase de Comer é mudar um lugar, e todos os animais passam a usar a frase nova.

A herança vai só para baixo

O gato herda do animal, não do cachorro. Os irmãos não trocam nada entre si:

var mia = new Gato { Nome = "Mia" };
mia.Latir();

class Animal
{
    public string Nome { get; set; } = "";
}

class Cachorro : Animal
{
    public void Latir()
    {
        Console.WriteLine($"{Nome}: Au au!");
    }
}

class Gato : Animal
{
}
$ dotnet run nao-late.cs
/home/ana/cs/nao-late.cs(2,5): error CS1061: ‘Gato’ não contém uma definição para "Latir" e não foi possível encontrar nenhum método de extensão "Latir" que aceite um primeiro argumento do tipo ‘Gato’ (você está se esquecendo de usar uma diretiva ou uma referência de assembly?)

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

"Gato não contém uma definição para Latir": é a mensagem de sempre quando um método não existe naquele tipo (o resto da frase, sobre "métodos de extensão", você pode ignorar por enquanto). Se todo animal precisa emitir um som, o método vai na classe Animal, e cada bicho faz do seu jeito. Isso é assunto de virtual e override.

Missão: crie a classe Passaro, que herda de Animal e tem um método Cantar() que mostra o nome seguido de : Piu piu!. O programa de cima já está pronto.

var tico = new Passaro { Nome = "Tico" };
tico.Comer();
tico.Cantar();

class Animal
{
    public string Nome { get; set; } = "";

    public void Comer()
    {
        Console.WriteLine($"{Nome} está comendo.");
    }
}

// escreva a classe Passaro aqui
var tico = new Passaro { Nome = "Tico" };
tico.Comer();
tico.Cantar();

class Animal
{
    public string Nome { get; set; } = "";

    public void Comer()
    {
        Console.WriteLine($"{Nome} está comendo.");
    }
}

class Passaro : Animal
{
    public void Cantar()
    {
        Console.WriteLine($"{Nome}: Piu piu!");
    }
}

O teste do "é um"

Herança não é só um truque para economizar código. Ela diz uma coisa sobre o mundo: "todo Cachorro é um Animal". Antes de escrever class X : Y, leia a frase em voz alta:

FraseFaz sentido?Herança?
Um cachorro é um animal.Simclass Cachorro : Animal
Uma conta poupança é uma conta.Simclass ContaPoupanca : Conta
Um professor é uma pessoa.Simclass Professor : Pessoa
Um carro é um motor.Não: um carro tem um motor.Não. O carro tem uma propriedade Motor.
Uma turma é um aluno.Não: uma turma tem alunos.Não. A turma tem uma List<Aluno>.

Quando a frase certa é "tem um", a resposta é colocar um objeto dentro do outro (isso se chama composição), e não herdar. Herdar de Motor só para "aproveitar" os métodos dele daria um carro que, para o C#, é um motor, e isso confunde todo mundo.


O que é herdado (e o que não é)

O private existe, mas a filha não enxerga

Um objeto Cachorro tem tudo o que um Animal tem, inclusive os campos private. Mas o código escrito dentro de Cachorro não pode tocar neles: private é só da própria classe, e a classe derivada é outra classe (lembre de public, private e internal).

var rex = new Cachorro();
rex.Brincar();

class Animal
{
    private int _energia = 10;

    public int Energia => _energia;

    public void Descansar()
    {
        _energia += 5;
    }
}

class Cachorro : Animal
{
    public void Brincar()
    {
        _energia -= 3;
    }
}
$ dotnet run privado-base.cs
/home/ana/cs/privado-base.cs(20,9): error CS0122: "Animal._energia" é inacessível devido ao seu nível de proteção

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

Os métodos herdados, porém, continuam mexendo no campo, porque eles foram escritos dentro de Animal. Sem o Brincar:

var rex = new Cachorro();
rex.Descansar();
Console.WriteLine($"Energia do Rex: {rex.Energia}");

class Animal
{
    private int _energia = 10;

    public int Energia => _energia;

    public void Descansar()
    {
        _energia += 5;
    }
}

class Cachorro : Animal
{
}
$ dotnet run privado-ok.cs
Energia do Rex: 15

O _energia do Rex existe (começou em 10 e foi para 15), mas só o código de Animal mexe nele. Para deixar a filha usar, a base tem duas saídas: oferecer um método public ou protected que faça a mudança com regras, ou marcar o campo como protected, o "armário da família".

Membro da baseO objeto da filha tem?O código da filha usa?Quem está de fora usa?
publicSimSimSim
protectedSimSimNão
privateSimNão (CS0122)Não
ConstrutoresNão são herdados (veja abaixo)

Os construtores não são herdados

Quando um objeto da filha nasce, o construtor da base roda antes, para montar a "parte animal" do cachorro. Depois roda o da filha:

var rex = new Cachorro();

class Animal
{
    public Animal()
    {
        Console.WriteLine("1. Construtor de Animal");
    }
}

class Cachorro : Animal
{
    public Cachorro()
    {
        Console.WriteLine("2. Construtor de Cachorro");
    }
}
$ dotnet run ordem-ctor.cs
1. Construtor de Animal
2. Construtor de Cachorro

Mas o construtor da base não vira construtor da filha. Se o único construtor de Animal pede um nome, o Cachorro fica sem saber como chamá-lo:

var rex = new Cachorro("Rex");

class Animal
{
    public Animal(string nome)
    {
        Nome = nome;
    }

    public string Nome { get; }
}

class Cachorro : Animal
{
}
$ dotnet run ctor-param.cs
/home/ana/cs/ctor-param.cs(13,7): error CS7036: Não há nenhum argumento fornecido que corresponda ao parâmetro necessário 'nome' de 'Animal.Animal(string)'
/home/ana/cs/ctor-param.cs(1,15): error CS1729: "Cachorro" não contém um construtor que aceita 1 argumentos

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

Dois erros:

  • CS7036, na linha 13 (class Cachorro): para montar a parte animal, o C# tentaria chamar Animal() sem nada, mas Animal exige um nome.
  • CS1729, na linha 1: Cachorro não tem construtor que receba um texto. O de Animal não foi herdado.

O conserto é escrever um construtor em Cachorro que passe o nome para o de Animal, com a palavra base. É exatamente o assunto da próxima aula.


Herança em vários níveis

Uma classe derivada pode ser base de outra. O cachorro é um mamífero, que é um animal:

var rex = new Cachorro { Nome = "Rex" };
rex.Comer();
rex.Mamar();
rex.Latir();

Console.WriteLine(rex is Cachorro);
Console.WriteLine(rex is Mamifero);
Console.WriteLine(rex is Animal);
Console.WriteLine(rex is object);

class Animal
{
    public string Nome { get; set; } = "";

    public void Comer()
    {
        Console.WriteLine($"{Nome} está comendo.");
    }
}

class Mamifero : Animal
{
    public void Mamar()
    {
        Console.WriteLine($"{Nome} mamou quando era filhote.");
    }
}

class Cachorro : Mamifero
{
    public void Latir()
    {
        Console.WriteLine($"{Nome}: Au au!");
    }
}
$ dotnet run niveis.cs
Rex está comendo.
Rex mamou quando era filhote.
Rex: Au au!
True
True
True
True

Cachorro recebe tudo de Mamifero, que já tinha recebido tudo de Animal. O operador is ("é") responde à pergunta do "é um": o Rex é cachorro, é mamífero, é animal e é object. Esse último você vai entender já já.

Não exagere nos níveis. Dois ou três níveis são comuns. Com seis ou sete, para entender o que um Cachorro faz, você precisa abrir seis classes. Prefira hierarquias rasas.

Uma classe base só

Em C#, cada classe herda de uma classe só. Um pato é animal e é ave, mas não pode herdar das duas:

var donald = new Pato();

class Animal
{
}

class Ave
{
}

class Pato : Animal, Ave
{
}
$ dotnet run duas-bases.cs
/home/ana/cs/duas-bases.cs(11,22): error CS1721: Classe "Pato" não pode ter várias classes base: "Animal" e "Ave"

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

A saída é organizar em níveis (Ave : Animal e Pato : Ave) ou usar interfaces: uma classe pode ter uma classe base e várias interfaces, que são "contratos" do que ela sabe fazer (class Pato : Ave, INadador, IVoador).


No topo de tudo: object

Se você não escreve : AlgumaCoisa, a classe herda, sem você ver, de object (o nome completo é System.Object). Toda classe do C# está, direta ou indiretamente, embaixo dela. É de lá que vêm métodos que todo objeto tem, como ToString() e GetType():

var rex = new Cachorro { Nome = "Rex" };
Console.WriteLine(rex.ToString());
Console.WriteLine(rex.GetType().Name);
Console.WriteLine(rex.GetType().BaseType);

class Animal
{
    public string Nome { get; set; } = "";
}

class Cachorro : Animal
{
}
$ dotnet run object.cs
Cachorro
Cachorro
Animal
rex.ToString()
Nem Cachorro nem Animal escreveram um ToString; ele veio de object. O padrão devolve o nome da classe, o que não é muito útil. Em A Classe object você aprende a trocá-lo por algo como "Rex (cachorro)".
rex.GetType().Name
O tipo verdadeiro do objeto, Cachorro.
rex.GetType().BaseType
A classe base dele, Animal. A de Animal seria System.Object.
E se uma classe não deve ter filhas? Marque-a com sealed ("lacrada"): sealed class Cachorro : Animal. Aí ninguém mais pode herdar dela. Veja sealed.

Quando dá errado

error CS1061: "…" não contém uma definição para "…"

O que aconteceu: você chamou, num objeto, um método que o tipo dele não tem nem herdou. Com herança, o caso típico é chamar o método de uma classe irmã (mia.Latir()) ou o da filha numa variável do tipo da base (Animal bicho = new Cachorro(); bicho.Latir();, assunto de Polimorfismo).

Como resolver: se todos precisam do método, coloque-o na classe base. Se não, chame-o só em quem o tem.

error CS0122 dentro da classe derivada

O que aconteceu: a filha tentou usar um membro private da base.

Como resolver: use um método public ou protected da base, ou marque o membro como protected.

error CS1721: Classe "…" não pode ter várias classes base

O que aconteceu: duas classes depois dos dois-pontos.

Como resolver: escolha uma base e organize as outras em níveis ou em interfaces.

error CS7036 e CS1729 ao criar a filha

O que aconteceu: a base só tem construtor com parâmetros, e a filha não diz como chamá-lo; ou você tentou usar na filha um construtor que só existe na base.

Como resolver: escreva um construtor na filha que chame o da base com : base(…). Veja base.


Referência

EscritaSignificado
class Cachorro : Animal { … }Cachorro herda de Animal
Classe baseA que dá as características (mãe, superclasse)
Classe derivadaA que herda (filha, subclasse)
obj is Animaltrue se o objeto é um Animal (ou de uma classe derivada)
protectedMembro visível para a classe e para as derivadas
obj.GetType().BaseTypeA classe base do tipo do objeto
object (System.Object)A base de todas as classes
sealed class XClasse que não pode ter derivadas

Missão final: a garagem da Ana

Missão: as classes Carro e Moto repetem Modelo, Rodas e Mostrar(). Crie uma classe base Veiculo com essas três coisas, faça Carro e Moto herdarem dela e deixe em cada uma só o método que é dela (Buzinar e Empinar).

var fusca = new Carro { Modelo = "Fusca", Rodas = 4 };
var biz = new Moto { Modelo = "Biz", Rodas = 2 };
fusca.Mostrar();
fusca.Buzinar();
biz.Mostrar();
biz.Empinar();

class Carro
{
    public string Modelo { get; set; } = "";
    public int Rodas { get; set; }

    public void Mostrar()
    {
        Console.WriteLine($"{Modelo}: {Rodas} rodas");
    }

    public void Buzinar()
    {
        Console.WriteLine($"{Modelo}: Biii!");
    }
}

class Moto
{
    public string Modelo { get; set; } = "";
    public int Rodas { get; set; }

    public void Mostrar()
    {
        Console.WriteLine($"{Modelo}: {Rodas} rodas");
    }

    public void Empinar()
    {
        Console.WriteLine($"{Modelo} empinou!");
    }
}
var fusca = new Carro { Modelo = "Fusca", Rodas = 4 };
var biz = new Moto { Modelo = "Biz", Rodas = 2 };
fusca.Mostrar();
fusca.Buzinar();
biz.Mostrar();
biz.Empinar();

class Veiculo
{
    public string Modelo { get; set; } = "";
    public int Rodas { get; set; }

    public void Mostrar()
    {
        Console.WriteLine($"{Modelo}: {Rodas} rodas");
    }
}

class Carro : Veiculo
{
    public void Buzinar()
    {
        Console.WriteLine($"{Modelo}: Biii!");
    }
}

class Moto : Veiculo
{
    public void Empinar()
    {
        Console.WriteLine($"{Modelo} empinou!");
    }
}

Exercícios

1. A sintaxe da herança

Complete a declaração para que Professor herde de Pessoa: class Professor ___

2. O topo da escada

De qual classe toda classe do C# herda, mesmo sem escrever nada? (a palavra-chave em minúsculas)

3. Quantas bases?

Quantas classes base uma classe do C# pode ter? (um número)

4. Qual destes pares não deveria usar herança?

5. Animal tem private int _energia. Dentro de Cachorro : Animal, o que acontece com _energia -= 3;?

6. Ao criar um Cachorro, em que ordem rodam os construtores?


Resumo

  • Herança evita código repetido: o comum vai na classe base (Animal), e cada classe derivada (Cachorro : Animal) acrescenta o que é dela.
  • Use herança só quando a frase "X é um Y" faz sentido. Para "tem um", coloque um objeto dentro do outro.
  • A filha herda o que é public e protected. O private existe no objeto, mas só o código da base mexe nele.
  • Construtores não são herdados. O da base roda antes do da filha; se ele pede parâmetros, a filha precisa de : base(…) (próxima aula).
  • Herança pode ter vários níveis, mas cada classe tem uma base só (CS1721). No topo de tudo está object.