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

CSHARP
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();
    }
}
▶ Experimente
TEXT 📖 Somente leitura
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

CSHARP
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();
    }
}
▶ Experimente
TEXT 📖 Somente leitura
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

CSHARP 📖 Somente leitura
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}");
        }
    }
}
44 linhas de lógica (limite de 40, somente leitura)
TEXT 📖 Somente leitura
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

CSHARP
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();
    }
}
▶ Experimente
TEXT 📖 Somente leitura
Hello from Derived
Hello from Base
💡 Diferença entre a ocultação de 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

CSHARP
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();
    }
}
▶ Experimente
TEXT 📖 Somente leitura
Loading JSON config...
⚠️ Assim que 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

CSHARP
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");
    }
}
▶ Experimente
TEXT 📖 Somente leitura
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

CSHARP
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);
    }
}
▶ Experimente
TEXT 📖 Somente leitura
(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

CSHARP
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}");
    }
}
▶ Experimente
TEXT 📖 Somente leitura
a is a Cat
a is not a Dog
Meow!
as Dog result is null: True
💡 O C# 7+ oferece suporte à correspondência de padrões 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 adicionar new para declará-lo explicitamente.

P: A palavra-chave base pode ser usada em métodos estáticos? R: Não. Tanto base quanto this se 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 is e as? R: is retorna um valor booleano para verificação de tipo; as tenta uma conversão e retorna o objeto ou null. as não pode ser usado com tipos de valor.

📖 Resumo

📝 Exercícios

  1. Defina uma classe base Employee (com as propriedades Name e Salary), crie uma classe derivada Manager (com uma propriedade adicional Level), e use base no construtor Manager para chamar o construtor da classe pai.
  2. Defina um método virtual void Work() em Employee, sobrescreva-o com override em Manager e chame base.Work() dentro do método sobrescrito.
  3. Crie uma matriz Employee[] que contenha os objetos Employee e Manager; use o operador is para determinar o tipo e exiba informações diferentes de acordo com o resultado.
  4. Sobrescreva o método ToString() em Manager para retornar uma string contendo Name, Salary e Level.
  5. Declare uma classe sealed como PayCalculator e, em seguida, tente criar uma classe que herde dela e observe o erro de compilação.
Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%