C#: Classes abstratas e interfaces

1. Classes abstratas e métodos abstratos

As classes abstratas são declaradas com o modificador abstract e não podem ser instanciadas. Elas podem conter tanto métodos abstratos quanto métodos concretos. Os métodos abstratos não possuem corpo de método e devem ser sobrescritos nas classes derivadas.

▶ Exemplo

CSHARP 📖 Somente leitura
abstract class Shape
{
    public string Name { get; set; }

    public abstract double Area();

    public void Print()
    {
        Console.WriteLine($"{Name} Area: {Area():F2}");
    }
}

class Circle : Shape
{
    public double Radius { get; set; }

    public Circle(double radius)
    {
        Name = "Circle";
        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)
    {
        Name = "Rectangle";
        Width = w;
        Height = h;
    }

    public override double Area()
    {
        return Width * Height;
    }
}

Circle c = new Circle(3);
Rectangle r = new Rectangle(4, 5);
c.Print();
r.Print();
41 linhas de lógica (limite de 40, somente leitura)
TEXT 📖 Somente leitura
Circle Area: 28.27
Rectangle Area: 20.00

💡 Uma classe abstrata é como um “modelo pela metade” — ela especifica o que as subclasses devem completar (métodos abstratos) e também fornece partes que podem ser usadas diretamente (métodos concretos).


2. Classes abstratas x classes comuns

Característica Classe regular Classe abstrata
Instanciação Sim Não
Métodos abstratos Não podem conter Podem conter
Métodos concretos Sim Sim
Construtor Sim Sim
Campos Sim Sim
Quando herdado Substituição opcional de método virtual Deve substituir método abstrato
modificador selado Sim Não (contraditório)

▶ Exemplo

CSHARP
abstract class Logger
{
    protected string prefix;

    public Logger(string prefix)
    {
        this.prefix = prefix;
    }

    public abstract void Log(string message);

    public void LogWithTimestamp(string message)
    {
        string ts = DateTime.Now.ToString("HH:mm:ss");
        Log($"[{ts}] {message}");
    }
}

class FileLogger : Logger
{
    public FileLogger() : base("FILE") { }

    public override void Log(string message)
    {
        Console.WriteLine($"{prefix}: {message}");
    }
}

FileLogger fl = new FileLogger();
fl.Log("Service started");
fl.LogWithTimestamp("Database connected");
▶ Experimente
TEXT 📖 Somente leitura
FILE: Service started
FILE: [14:30:00] Database connected

⚠️ As classes abstratas podem ter construtores, mas os construtores não podem ser abstract. Os construtores são chamados por meio de base() quando uma subclasse é instanciada.


3. Definição e implementação da interface

As interfaces são definidas por meio da palavra-chave interface e representam um contrato de membros. As classes que implementam uma interface devem fornecer implementações para todos os seus membros. Os membros da interface são implicitamente public e não podem ter modificadores de acesso.

▶ Exemplo

CSHARP
interface IMovable
{
    void Move(double dx, double dy);
    double Speed { get; set; }
}

interface IDrawable
{
    void Draw();
}

class Player : IMovable, IDrawable
{
    public double X { get; set; }
    public double Y { get; set; }
    public double Speed { get; set; }

    public Player(double x, double y, double speed)
    {
        X = x;
        Y = y;
        Speed = speed;
    }

    public void Move(double dx, double dy)
    {
        X += dx * Speed;
        Y += dy * Speed;
        Console.WriteLine($"Moved to ({X:F1}, {Y:F1})");
    }

    public void Draw()
    {
        Console.WriteLine($"Drawing player @ ({X:F1}, {Y:F1})");
    }
}

Player p = new Player(0, 0, 2.0);
p.Draw();
p.Move(3, 4);
p.Draw();
▶ Experimente
TEXT 📖 Somente leitura
Drawing player @ (0.0, 0.0)
Moved to (6.0, 8.0)
Drawing player @ (6.0, 8.0)

📌 Os nomes das interfaces devem começar com uma letra maiúscula I, como IComparable, IDisposable — essa é uma convenção de nomenclatura do C#.


4. Implementação de múltiplas interfaces

Uma classe pode implementar várias interfaces, separadas por vírgulas. Essa é uma das formas pelas quais o C# alcança o polimorfismo, compensando a limitação da herança única.

▶ Exemplo

CSHARP
interface IWalkable
{
    void Walk();
}

interface ISwimmable
{
    void Swim();
}

interface IFlyable
{
    void Fly();
}

class Duck : IWalkable, ISwimmable, IFlyable
{
    public string Name { get; set; }

    public Duck(string name)
    {
        Name = name;
    }

    public void Walk()
    {
        Console.WriteLine($"{Name} waddles along");
    }

    public void Swim()
    {
        Console.WriteLine($"{Name} swims in the water");
    }

    public void Fly()
    {
        Console.WriteLine($"{Name} flaps its wings and takes off");
    }
}

Duck d = new Duck("Donald");
d.Walk();
d.Swim();
d.Fly();

IWalkable walker = d;
walker.Walk();

ISwimmable swimmer = d;
swimmer.Swim();
▶ Experimente
TEXT 📖 Somente leitura
Donald waddles along
Donald swims in the water
Donald flaps its wings and takes off
Donald waddles along
Donald swims in the water

💡 Variáveis de diferentes tipos de interface só podem chamar membros definidos por essa interface — isso reflete o Princípio da Segregação de Interfaces.


5. Métodos padrão de interface (C# 8)

A partir do C# 8, as interfaces podem conter métodos com implementações padrão. Isso permite adicionar novos membros a uma interface sem comprometer as classes que já a implementam.

▶ Exemplo

CSHARP
interface ILogger
{
    void Log(string message);

    void LogWarning(string message)
    {
        Log($"[WARN] {message}");
    }

    void LogError(string message)
    {
        Log($"[ERROR] {message}");
    }
}

class ConsoleLogger : ILogger
{
    public void Log(string message)
    {
        Console.WriteLine(message);
    }
}

class SimpleLogger : ILogger
{
    public void Log(string message)
    {
        Console.WriteLine($">> {message}");
    }
}

ConsoleLogger cl = new ConsoleLogger();
cl.Log("System started");
cl.LogWarning("Low memory");
cl.LogError("Connection failed");

SimpleLogger sl = new SimpleLogger();
sl.Log("System started");
sl.LogWarning("Low memory");
▶ Experimente
TEXT 📖 Somente leitura
System started
[WARN] Low memory
[ERROR] Connection failed
>> System started
>> [WARN] Low memory

⚠️ Os métodos padrão só estão disponíveis quando chamados por meio do tipo de interface. Eles não podem ser chamados por meio de uma variável do tipo da classe implementadora (a menos que a classe implementadora também reimplemente o método).


6. Guia de seleção entre classe abstrata e interface

Comparação Classe abstrata Interface
Herança/Implementação Herança única Implementação múltipla
Tipos de membros Qualquer Métodos, propriedades, eventos, indexadores
Campos Sim Não (antes do C# 8)
Construtor Sim Não
Modificadores de acesso Qualquer Padrão: público
Implementação padrão Sim A partir do C# 8
Compatibilidade entre versões A adição de membros é segura A adição de membros causa falhas nas implementações (antes do C# 8)
Objetivo do projeto “O que é” (IS-A) “O que pode fazer” (CAN-DO)

Diretrizes de seleção:

🔥 Regra rápida: Use classes abstratas para identidade compartilhada e interfaces para capacidade compartilhada.


7. Implementação explícita de interface

Quando várias interfaces possuem membros com o mesmo nome, ou quando for necessário ocultar membros de uma interface, utilize a implementação explícita da interface. A sintaxe é return_type InterfaceName.MemberName. Os membros implementados explicitamente não podem ter modificadores de acesso e só podem ser chamados por meio do tipo da interface.

▶ Exemplo

CSHARP
interface IEnglish
{
    void Speak();
}

interface IFrench
{
    void Speak();
}

class BilingualPerson : IEnglish, IFrench
{
    void IEnglish.Speak()
    {
        Console.WriteLine("Hello!");
    }

    void IFrench.Speak()
    {
        Console.WriteLine("Bonjour!");
    }

    public void Speak()
    {
        Console.WriteLine("Hi!");
    }
}

BilingualPerson p = new BilingualPerson();
p.Speak();

IEnglish eng = p;
eng.Speak();

IFrench fr = p;
fr.Speak();
▶ Experimente
TEXT 📖 Somente leitura
Hi!
Hello!
Bonjour!

📌 Os membros de interface implementados explicitamente não são visíveis na instância da classe — é necessário fazer um cast para o tipo de interface correspondente para chamá-los. Isso é tanto uma limitação quanto um mecanismo de encapsulamento.


8. IComparable<T> na prática

IComparable<T> é uma das interfaces mais utilizadas no .NET, empregada para definir a ordem natural de classificação de um tipo. Tanto Array.Sort quanto List<T>.Sort` dependem dessa interface.

▶ Exemplo

CSHARP
class Student : IComparable<Student>
{
    public string Name { get; set; }
    public int Score { get; set; }

    public Student(string name, int score)
    {
        Name = name;
        Score = score;
    }

    public int CompareTo(Student other)
    {
        if (other == null) return 1;
        return Score.CompareTo(other.Score);
    }

    public override string ToString()
    {
        return $"{Name}({Score})";
    }
}

Student[] students = new Student[]
{
    new Student("Alice", 85),
    new Student("Bob", 92),
    new Student("Charlie", 78),
    new Student("Diana", 95),
    new Student("Eve", 88)
};

Console.WriteLine("Before sorting:");
foreach (var s in students)
    Console.WriteLine(s);

Array.Sort(students);

Console.WriteLine("After sorting (by score ascending):");
foreach (var s in students)
    Console.WriteLine(s);
▶ Experimente
TEXT 📖 Somente leitura
Before sorting:
Alice(85)
Bob(92)
Charlie(78)
Diana(95)
Eve(88)
After sorting (by score ascending):
Charlie(78)
Alice(85)
Eve(88)
Bob(92)
Diana(95)

💻 Convenção de valor de retorno de CompareTo: um valor negativo significa que o objeto atual vem antes do outro; zero significa que são iguais; e um valor positivo significa que o objeto atual vem depois. Com IComparable<T>, tipos personalizados podem usar diretamente Array.Sort, List<T>.Sort` e outros recursos de classificação.

❓ Perguntas Frequentes

P: Uma classe abstrata pode implementar uma interface? R: Sim. Uma classe abstrata pode implementar uma interface e pode optar por implementar seus membros de forma concreta ou deixá-los como abstratos.

P: Uma interface pode herdar de outra interface? R: Sim. Uma interface pode herdar de várias interfaces, e a classe que a implementa deve implementar todos os membros ao longo da cadeia de herança.

P: Uma implementação explícita de interface pode ter um modificador de acesso público? R: Não. As implementações explícitas de interface não podem ter nenhum modificador de acesso — elas só são acessíveis por meio do tipo de interface.

P: Qual é a diferença entre um método padrão de interface e um método virtual de classe abstrata? R: Os métodos padrão de interface não podem acessar campos de instância, enquanto os métodos virtuais de classe abstrata podem; os métodos padrão são chamados por meio da interface, enquanto os métodos virtuais são chamados por meio de instâncias da classe.

📖 Resumo

📝 Exercícios

  1. Defina uma classe abstrata Vehicle com um método abstrato Start() e um método concreto Stop(); em seguida, crie as subclasses Car e Bike para implementá-la
  2. Defina uma interface ISerializable com o método string Serialize() e uma interface IDeserializable com o método void Deserialize(string data); em seguida, crie uma classe que implemente ambas as interfaces
  3. Defina duas interfaces IPlayable e IRecordable, ambas com um método void Pause(), e utilize a implementação explícita de interface para diferenciá-las
  4. Crie uma classe Product que implemente IComparable<Product> para ordenar por preço e teste com Array.Sort
  5. Adicione um método void PrintHeader(string title) com uma implementação padrão a uma interface IPrintable já existente e verifique se as classes que já implementavam essa interface não são afetadas
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%