C#: Herança
1. O que é herança
A herança é um mecanismo fundamental da programação orientada a objetos que permite que uma classe reutilize os membros de outra classe e a amplie com novas funcionalidades. O C# suporta apenas herança simples, o que significa que uma classe pode ter apenas uma classe base direta.
2. Sintaxe de herança
Use class DerivedClass : BaseClass para declarar uma relação de herança. A classe derivada adquire automaticamente todos os membros não privados da classe base.
▶ Exemplo
class Animal
{
public string Name { get; set; }
public void Eat()
{
Console.WriteLine($"{Name} is eating.");
}
}
class Dog : Animal
{
public void Bark()
{
Console.WriteLine($"{Name}: Woof!");
}
}
class Program
{
static void Main()
{
Dog dog = new Dog { Name = "Buddy" };
dog.Eat();
dog.Bark();
}
}
Buddy is eating.
Buddy: Woof!
3. A palavra-chave “base”
base é usado para chamar os construtores e os métodos membros da classe base. Em um construtor, passe argumentos para o construtor pai por meio de base(args); em um método, chame a implementação pai por meio de base.Method().
▶ Exemplo
class Person
{
public string Name { get; }
public Person(string name)
{
Name = name;
}
public virtual void Introduce()
{
Console.WriteLine($"I am {Name}.");
}
}
class Student : Person
{
public int Grade { get; }
public Student(string name, int grade) : base(name)
{
Grade = grade;
}
public override void Introduce()
{
base.Introduce();
Console.WriteLine($"I am in grade {Grade}.");
}
}
class Program
{
static void Main()
{
Student s = new Student("Alice", 3);
s.Introduce();
}
}
I am Alice.
I am in grade 3.
4. Sobrescrita de métodos com virtual/override
A classe base identifica os métodos que podem ser sobrescritos com virtual, e a classe derivada fornece uma nova implementação usando override. Essa é a base do polimorfismo.
▶ Exemplo
class Shape
{
public virtual double Area()
{
return 0;
}
}
class Circle : Shape
{
public double Radius { get; set; }
public Circle(double radius)
{
Radius = radius;
}
public override double Area()
{
return Math.PI * Radius * Radius;
}
}
class Rectangle : Shape
{
public double Width { get; set; }
public double Height { get; set; }
public Rectangle(double w, double h)
{
Width = w;
Height = h;
}
public override double Area()
{
return Width * Height;
}
}
class Program
{
static void Main()
{
Shape[] shapes = { new Circle(2), new Rectangle(3, 4) };
foreach (Shape s in shapes)
{
Console.WriteLine($"Area: {s.Area():F2}");
}
}
}
Area: 12.57
Area: 12.00
5. Ocultação de métodos com new
Se uma classe derivada definir um método com o mesmo nome que um método da classe base sem usar override, o compilador emitirá um aviso. A palavra-chave new oculta explicitamente o método da classe base, indicando que se trata de um método independente e não de uma substituição.
▶ Exemplo
class Base
{
public void Greet()
{
Console.WriteLine("Hello from Base");
}
}
class Derived : Base
{
public new void Greet()
{
Console.WriteLine("Hello from Derived");
}
}
class Program
{
static void Main()
{
Derived d = new Derived();
d.Greet();
Base b = d;
b.Greet();
}
}
Hello from Derived
Hello from Base
new e a substituição de override: quando chamado por meio de uma referência à classe base, new executa a versão da classe base, enquanto override executa a versão da classe derivada.
6. sealed: Impedindo a herança
Aplicar sealed a uma classe significa que ela não pode ser herdada; aplicá-lo a um método significa que o método não pode ser mais sobrescrito.
▶ Exemplo
class Config
{
public virtual void Load()
{
Console.WriteLine("Loading config...");
}
}
class JsonConfig : Config
{
public sealed override void Load()
{
Console.WriteLine("Loading JSON config...");
}
}
sealed class FinalClass
{
public void DoWork() { }
}
class Program
{
static void Main()
{
JsonConfig cfg = new JsonConfig();
cfg.Load();
}
}
Loading JSON config...
JsonConfig.Load for marcado como sealed, nenhuma classe derivada de JsonConfig poderá mais sobrescrever Load. Assim que FinalClass for marcado como sealed, nenhuma classe poderá mais herdar dele.
7. Encadeamento de construtores na herança
Quando um objeto de uma classe derivada é criado, o construtor da classe base é sempre executado antes do construtor da classe derivada. Se a classe base não tiver um construtor sem parâmetros, a classe derivada deve chamar explicitamente base(args).
▶ Exemplo
class Vehicle
{
public string Type { get; }
public Vehicle(string type)
{
Type = type;
Console.WriteLine($"Vehicle({type}) constructed");
}
}
class Car : Vehicle
{
public string Brand { get; }
public Car(string brand) : base("Car")
{
Brand = brand;
Console.WriteLine($"Car({brand}) constructed");
}
}
class Program
{
static void Main()
{
Car car = new Car("Toyota");
}
}
Vehicle(Car) constructed
Car(Toyota) constructed
8. A classe raiz do objeto
Todos os tipos em C# herdam de System.Object (também conhecido como object). Ele fornece métodos fundamentais, como ToString(), Equals(), GetHashCode() e GetType().
▶ Exemplo
class Point
{
public int X { get; }
public int Y { get; }
public Point(int x, int y)
{
X = x;
Y = y;
}
public override string ToString()
{
return $"({X}, {Y})";
}
public override bool Equals(object obj)
{
if (obj is Point other)
{
return X == other.X && Y == other.Y;
}
return false;
}
public override int GetHashCode()
{
return HashCode.Combine(X, Y);
}
}
class Program
{
static void Main()
{
Point p1 = new Point(1, 2);
Point p2 = new Point(1, 2);
Console.WriteLine(p1.ToString());
Console.WriteLine(p1.Equals(p2));
Console.WriteLine(p1.GetType().Name);
}
}
(1, 2)
True
Point
9. Os operadores “is” e “as”
is realiza uma verificação de tipo e retorna um bool; as realiza uma conversão de tipo segura, retornando null em caso de falha, em vez de lançar uma exceção.
▶ Exemplo
class Animal { }
class Cat : Animal
{
public void Meow() { Console.WriteLine("Meow!"); }
}
class Dog : Animal
{
public void Bark() { Console.WriteLine("Woof!"); }
}
class Program
{
static void Main()
{
Animal a = new Cat();
if (a is Cat)
{
Console.WriteLine("a is a Cat");
}
if (a is Dog)
{
Console.WriteLine("a is a Dog");
}
else
{
Console.WriteLine("a is not a Dog");
}
Cat c = a as Cat;
if (c != null)
{
c.Meow();
}
Dog d = a as Dog;
Console.WriteLine($"as Dog result is null: {d == null}");
}
}
a is a Cat
a is not a Dog
Meow!
as Dog result is null: True
is: if (a is Cat cat) permite verificar o tipo e declarar uma variável ao mesmo tempo.
❓ Perguntas Frequentes
P: O C# suporta herança múltipla? R: Não. Uma classe só pode ter uma classe base direta, mas pode implementar várias interfaces.
P: O que acontece se eu escrever um método com o mesmo nome sem usar o
override? R: O compilador gera um aviso. O método oculta implicitamente o método da classe base; recomenda-se adicionarnewpara declará-lo explicitamente.
P: A palavra-chave
basepode ser usada em métodos estáticos? R: Não. Tantobasequantothisse referem a uma instância, e os métodos estáticos não possuem contexto de instância.
P: Qual é a importância prática de uma classe selada? R: Ela impede que a classe seja herdada, garantindo que sua implementação não possa ser modificada, e permite que o compilador JIT realize mais otimizações.
P: Qual é a diferença entre
iseas? R:isretorna um valor booleano para verificação de tipo;astenta uma conversão e retorna o objeto ounull.asnão pode ser usado com tipos de valor.
📖 Resumo
- A herança utiliza a sintaxe
class Derived : Base; o C# suporta apenas herança simples - A palavra-chave
basechama o construtor e os métodos membros da classe pai virtual/overrideimplementa a sobrescrita de métodos, que é a base do polimorfismo- A palavra-chave
newoculta explicitamente um método da classe base, comportando-se de maneira diferente da sobrescrita sealedquando aplicado a uma classe, impede a herança; quando aplicado a um método, impede que ele seja substituído novamente- Os construtores são executados na ordem da classe base para a classe derivada
- Todos os tipos são derivados de
object, e é possível sobrescreverToString,EqualseGetHashCode isrealiza a verificação de tipos;asrealiza a conversão segura
📝 Exercícios
- Defina uma classe base
Employee(com as propriedadesNameeSalary), crie uma classe derivadaManager(com uma propriedade adicionalLevel), e usebaseno construtorManagerpara chamar o construtor da classe pai. - Defina um método
virtual void Work()emEmployee, sobrescreva-o comoverrideemManagere chamebase.Work()dentro do método sobrescrito. - Crie uma matriz
Employee[]que contenha os objetosEmployeeeManager; use o operadorispara determinar o tipo e exiba informações diferentes de acordo com o resultado. - Sobrescreva o método
ToString()emManagerpara retornar uma string contendoName,SalaryeLevel. - Declare uma classe
sealedcomoPayCalculatore, em seguida, tente criar uma classe que herde dela e observe o erro de compilação.