C# base
Na aula de Herança, o Cachorro não compilava quando o construtor de Animal pedia um nome. Faltava uma forma de a classe filha conversar com a mãe. Essa forma é a palavra base: com ela, a classe derivada chama o construtor da classe base e, quando precisa, os métodos dela.
- Por que a classe filha precisa chamar o construtor da base.
: base(…): passar dados para o construtor da base.- A ordem em que os construtores rodam, inclusive com vários níveis.
- Como escolher entre vários construtores da base, e a diferença entre
base(…)ethis(…). base.Metodo(): reaproveitar a versão da base de um método.- Os construtores primários C# 12:
class Cachorro(string nome) : Animal(nome). - Os erros
CS7036,CS1729eCS1511.
A ideia: montar a base primeiro
Pense numa casa de dois andares. Antes de erguer o segundo andar, alguém precisa construir o térreo, e o pedreiro do térreo precisa de informações: o tamanho do terreno, onde fica a porta. O pedreiro do segundo andar não pode simplesmente começar: ele primeiro passa as informações para o pedreiro do térreo e espera ele terminar.
Um objeto Cachorro é assim. Ele tem uma "parte animal" (o térreo, que vem de Animal) e uma "parte cachorro" (o andar de cima). Quando você faz new Cachorro(…), o construtor de Animal roda primeiro. Se ele precisa de um nome, o construtor de Cachorro é quem tem de entregar: : base(nome).
O problema: o construtor da base pede dados
var rex = new Cachorro("Rex", "vira-lata");
Console.WriteLine($"{rex.Nome} é um {rex.Raca}.");
class Animal
{
public Animal(string nome)
{
Nome = nome;
}
public string Nome { get; }
}
class Cachorro : Animal
{
public Cachorro(string nome, string raca)
{
Raca = raca;
}
public string Raca { get; }
}
$ dotnet run sem-base.cs /home/ana/cs/sem-base.cs(16,12): error CS7036: Não há nenhum argumento fornecido que corresponda ao parâmetro necessário 'nome' de 'Animal.Animal(string)' Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
O erro aponta o construtor de Cachorro (linha 16). Sem instrução nenhuma, o C# tenta montar a parte animal chamando Animal(), sem argumentos. Mas o único construtor de Animal exige o parâmetro nome: "não há nenhum argumento fornecido que corresponda ao parâmetro necessário 'nome'". Em inglês: There is no argument given that corresponds to the required parameter 'nome' of 'Animal.Animal(string)'.
Repare que o Cachorro até recebe o nome como parâmetro. Ele só não está entregando para ninguém.
: base(…), passando o nome para a base
var rex = new Cachorro("Rex", "vira-lata");
Console.WriteLine($"{rex.Nome} é um {rex.Raca}.");
class Animal
{
public Animal(string nome)
{
Console.WriteLine($"1. Animal montado: {nome}");
Nome = nome;
}
public string Nome { get; }
}
class Cachorro : Animal
{
public Cachorro(string nome, string raca) : base(nome)
{
Console.WriteLine($"2. Cachorro montado: {raca}");
Raca = raca;
}
public string Raca { get; }
}
$ dotnet run base-ctor.cs 1. Animal montado: Rex 2. Cachorro montado: vira-lata Rex é um vira-lata.
- public Cachorro(string nome, string raca)
- O construtor da filha recebe tudo o que precisa: o que é dela (
raca) e o que é da base (nome). - : base(nome)
- Fica entre os parênteses e a chave de abertura. Quer dizer: "antes do meu corpo, chame o construtor da classe base com estes argumentos". O
nomeaqui é o parâmetro que oCachorrorecebeu. - 1. Animal montado
- A prova da ordem: o corpo do construtor de
Animalroda primeiro… - 2. Cachorro montado
- …e só depois o corpo do construtor de
Cachorro. Quando ele começa, oNomejá está preenchido.
O base(…) funciona como uma chamada de método comum: você pode passar valores fixos (base("sem nome")), contas (base(nome.Trim())) ou qualquer parâmetro do construtor da filha.
Missão: o programa não compila, com o erro CS7036. Rode para ver. Depois, faça o construtor de Gato passar o nome para o construtor de Animal com : base(nome).
var mia = new Gato("Mia", 3);
Console.WriteLine($"{mia.Nome} tem {mia.Idade} anos.");
class Animal
{
public Animal(string nome)
{
Nome = nome;
}
public string Nome { get; }
}
class Gato : Animal
{
public Gato(string nome, int idade)
{
Idade = idade;
}
public int Idade { get; }
}
var mia = new Gato("Mia", 3);
Console.WriteLine($"{mia.Nome} tem {mia.Idade} anos.");
class Animal
{
public Animal(string nome)
{
Nome = nome;
}
public string Nome { get; }
}
class Gato : Animal
{
public Gato(string nome, int idade) : base(nome)
{
Idade = idade;
}
public int Idade { get; }
}
Com vários níveis
Cada classe conversa só com a mãe direta. O Cachorro passa o nome para o Mamifero, que passa para o Animal. Os corpos rodam de cima para baixo, do térreo ao último andar:
var rex = new Cachorro("Rex");
class Animal
{
public Animal(string nome)
{
Console.WriteLine($"Animal: {nome}");
}
}
class Mamifero : Animal
{
public Mamifero(string nome) : base(nome)
{
Console.WriteLine("Mamífero");
}
}
class Cachorro : Mamifero
{
public Cachorro(string nome) : base(nome)
{
Console.WriteLine("Cachorro");
}
}
$ dotnet run tres.cs Animal: Rex Mamífero Cachorro
Dentro de Cachorro, base é o Mamifero, e não o Animal. Não há como "pular" um andar.
Escolhendo o construtor da base
Se a base tem vários construtores (veja Sobrecarga), o base(…) escolhe qual chamar pelo número e pelo tipo dos argumentos, como em qualquer chamada:
var mia = new Gato("Mia");
var semNome = new Gato();
Console.WriteLine(mia.Nome);
Console.WriteLine(semNome.Nome);
class Animal
{
public Animal()
{
Nome = "(sem nome)";
}
public Animal(string nome)
{
Nome = nome;
}
public string Nome { get; }
}
class Gato : Animal
{
public Gato() : base()
{
}
public Gato(string nome) : base(nome)
{
}
}
$ dotnet run base-varios.cs Mia (sem nome)
Gato() chama Animal(), e Gato(string) chama Animal(string). O : base() vazio é opcional: sem nada, o C# já chama o construtor sem parâmetros da base. Ele está escrito aqui só para deixar a escolha à vista. Se não existir um construtor da base com aqueles argumentos, o erro é o CS1729:
$ dotnet run base-args.cs « com : base(nome, raca), mas Animal só recebe o nome /home/ana/cs/base-args.cs(15,49): error CS1729: "Animal" não contém um construtor que aceita 2 argumentos Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
base(…) ou this(…)?
Na aula this, você viu que um construtor pode chamar outro construtor da mesma classe com : this(…). As duas formas ficam no mesmo lugar, mas apontam para andares diferentes:
var mia = new Gato("Mia");
var tom = new Gato();
Console.WriteLine($"{mia.Nome}, {mia.Vidas} vidas");
Console.WriteLine($"{tom.Nome}, {tom.Vidas} vidas");
class Animal
{
public Animal(string nome)
{
Nome = nome;
}
public string Nome { get; }
}
class Gato : Animal
{
public Gato(string nome) : base(nome)
{
Vidas = 7;
}
public Gato() : this("Gato sem nome")
{
}
public int Vidas { get; }
}
$ dotnet run this-base.cs Mia, 7 vidas Gato sem nome, 7 vidas
- public Gato(string nome) : base(nome)
- Chama o construtor da mãe (
Animal). - public Gato() : this("Gato sem nome")
- Chama o outro construtor do próprio
Gato, que por sua vez chama a base. Assim, oVidas = 7fica escrito num lugar só.
Um construtor escolhe um dos dois: ou : base(…), ou : this(…).
base.Metodo(): a versão da base
base também serve fora dos construtores. Na próxima aula, você vai ver que a filha pode substituir um método da mãe por uma versão dela (com virtual e override). Muitas vezes, a versão nova quer fazer o que a mãe já fazia, e mais um pouco. É aí que entra base.Metodo():
var rex = new Cachorro("Rex", "vira-lata");
rex.Apresentar();
class Animal
{
public Animal(string nome)
{
Nome = nome;
}
public string Nome { get; }
public virtual void Apresentar()
{
Console.WriteLine($"Eu sou {Nome}.");
}
}
class Cachorro : Animal
{
public Cachorro(string nome, string raca) : base(nome)
{
Raca = raca;
}
public string Raca { get; }
public override void Apresentar()
{
base.Apresentar();
Console.WriteLine($"Sou um cachorro {Raca}.");
}
}
$ dotnet run base-metodo.cs Eu sou Rex. Sou um cachorro vira-lata.
- public virtual void Apresentar()
virtual: a base avisa que as filhas podem trocar este método. Detalhes na próxima aula.- public override void Apresentar()
override: oCachorrotroca oApresentarpela versão dele.- base.Apresentar();
- Dentro da versão nova, chama a versão da base, que mostra "Eu sou Rex.". Sem o
base., a linhaApresentar();chamaria a própria versão doCachorro, que chamaria de novo, e de novo, até o programa cair comStack overflow.. - Console.WriteLine($"Sou um cachorro {Raca}.");
- O "mais um pouco" que só o cachorro faz.
Assim, a frase "Eu sou …" fica escrita só em Animal. Se a Ana mudar essa frase, o cachorro passa a usar a nova sem mudar nada.
base não existe em métodos static
Como this, a palavra base fala do objeto atual. Num método static (veja static), não há objeto:
Cachorro.Teste();
class Animal
{
public string Nome { get; set; } = "";
}
class Cachorro : Animal
{
public static void Teste()
{
Console.WriteLine(base.Nome);
}
}
$ dotnet run base-static.cs /home/ana/cs/base-static.cs(12,27): error CS1511: A palavra-chave 'base' não está disponível em um método estático Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
O atalho: construtores primários C# 12
Quando o construtor só repassa valores, dá para escrever os parâmetros direto no nome da classe. Isso se chama construtor primário, e a chamada à base vai junto, depois dos dois-pontos:
var rex = new Cachorro("Rex", "vira-lata");
Console.WriteLine($"{rex.Nome} é um {rex.Raca}.");
class Animal(string nome)
{
public string Nome { get; } = nome;
}
class Cachorro(string nome, string raca) : Animal(nome)
{
public string Raca { get; } = raca;
}
$ dotnet run primario.cs Rex é um vira-lata.
- class Animal(string nome)
- Os parâmetros do construtor ficam ao lado do nome da classe. Dentro da classe,
nomepode ser usado para dar valor às propriedades. - class Cachorro(string nome, string raca) : Animal(nome)
- O
Animal(nome)depois dos dois-pontos faz o papel do: base(nome): diz qual a classe base e o que passar para ela.
É o mesmo programa do começo da aula, em bem menos linhas. Para classes grandes, ou com validação no construtor, a forma clássica com : base(…) continua sendo a mais clara. As duas aparecem muito em código real.
Quando dá errado
O que aconteceu: a base só tem construtor com parâmetros, e o construtor da filha não tem : base(…) (ou a filha nem tem construtor).
Como resolver: escreva o construtor da filha com : base(…), passando o que a base pede. Outra saída é dar à base um construtor sem parâmetros, se fizer sentido.
O que aconteceu: o base(…) (ou um new) passou um número de argumentos que nenhum construtor da classe aceita.
Como resolver: confira quais construtores a base tem e passe exatamente o que um deles pede.
O que aconteceu: base. dentro de um método static.
Como resolver: tire o static do método, ou use um objeto: rex.Nome.
O que aconteceu: a versão nova chamou a si mesma (Apresentar();) em vez da versão da base.
Como resolver: escreva base.Apresentar();.
Referência
| Escrita | Significado |
|---|---|
public Filha(…) : base(a, b) { … } | Chama o construtor da base antes do corpo |
public Filha() : base() { } | Chama o construtor sem parâmetros da base (é o que acontece sem nada escrito) |
public Filha() : this(…) { } | Chama outro construtor da própria classe |
base.Metodo() | Chama a versão da base de um método |
base.Propriedade | Usa uma propriedade da base |
class Filha(string x) : Mae(x) C# 12 | Construtor primário passando x para a base |
Missão final: a conta poupança
Missão: uma ContaPoupanca é uma Conta que rende juros. Escreva o construtor public ContaPoupanca(string titular, decimal taxa): ele passa o titular para a Conta com : base(titular) e guarda a taxa na propriedade Taxa. Repare no protected set do Saldo: é ele que deixa o RenderJuros da filha mudar o saldo.
var poupanca = new ContaPoupanca("Ana", 0.01m);
poupanca.Depositar(1000m);
poupanca.RenderJuros();
Console.WriteLine($"{poupanca.Titular}: {poupanca.Saldo:C}");
class Conta
{
public Conta(string titular)
{
Titular = titular;
}
public string Titular { get; }
public decimal Saldo { get; protected set; }
public void Depositar(decimal valor)
{
if (valor > 0)
{
Saldo += valor;
}
}
}
class ContaPoupanca : Conta
{
// escreva o construtor aqui
public decimal Taxa { get; }
public void RenderJuros()
{
Saldo += Saldo * Taxa;
}
}
var poupanca = new ContaPoupanca("Ana", 0.01m);
poupanca.Depositar(1000m);
poupanca.RenderJuros();
Console.WriteLine($"{poupanca.Titular}: {poupanca.Saldo:C}");
class Conta
{
public Conta(string titular)
{
Titular = titular;
}
public string Titular { get; }
public decimal Saldo { get; protected set; }
public void Depositar(decimal valor)
{
if (valor > 0)
{
Saldo += valor;
}
}
}
class ContaPoupanca : Conta
{
public ContaPoupanca(string titular, decimal taxa) : base(titular)
{
Taxa = taxa;
}
public decimal Taxa { get; }
public void RenderJuros()
{
Saldo += Saldo * Taxa;
}
}
Exercícios
1. Chamar a base
Complete o construtor para passar nome ao construtor da base: public Aluno(string nome, int serie) ___
2. A versão da mãe
Dentro de um override de Mostrar(), como se chama a versão da classe base? (com o ponto e vírgula)
3. O erro do argumento que falta
Qual é o código do erro quando a base exige um parâmetro e a filha não usa : base(…)?
4. new Cachorro("Rex"), com Cachorro(string nome) : base(nome). Qual corpo de construtor roda primeiro?
5. Qual a diferença entre : base(…) e : this(…) num construtor?
6. Em class Cachorro(string nome) : Animal(nome), o que é o Animal(nome)?
Resumo
- O construtor da base roda antes do da filha. Se ele pede dados, a filha entrega com
: base(…), entre os parênteses e a chave. - Sem
: base(…), o C# chama o construtor sem parâmetros da base; se ele não existe,CS7036. Argumentos que nenhum construtor aceita:CS1729. - Cada classe fala só com a mãe direta.
: this(…)chama outro construtor da mesma classe. base.Metodo()chama a versão da base, útil dentro de umoverride. Não existe em métodosstatic(CS1511).- Atalho do C# 12:
class Cachorro(string nome) : Animal(nome).