1. Rapidinho sobre as diferenças clássicas
Se alguém falar: "Interface é só um conjunto de assinaturas", pergunta: "Em qual versão do C# você tá codando?" A partir do C# 8 e mais recentes, as interfaces ficaram muito mais poderosas. Bora comparar elas com classes abstratas — não só pelas propriedades clássicas, mas também levando em conta todas as novidades da plataforma .NET.
Se a gente voltar alguns anos — lá no C# 7 — era tudo simples. Classe abstrata pode definir campos e métodos parcialmente implementados, interface — só assinaturas (de métodos, propriedades, eventos, indexadores).
Herança de classe abstrata rola pelo relacionamento "é um" (Is-a), interfaces implementam herança múltipla de comportamento ("sabe fazer", can-do).
| Característica | Classe abstrata | Interface (até C# 8) |
|---|---|---|
| Relacionamento | is-a | can-do |
| Herança | Só uma | Múltipla |
| Campos | Pode ter | Não pode |
| Implementação de métodos | Pode | Não pode |
| Construtores | Pode | Não pode |
| Modificadores de acesso | Diversos (public, protected, ...) | Só implicitamente public |
Como dá pra ver, antes as classes abstratas eram tipo "irmão mais velho" das interfaces — mais poderosas e flexíveis. Mas tudo muda!
2. Interfaces com implementação padrão
Com o lançamento do C# 8 (e ainda mais no C# 14 e .NET 9) as interfaces ganharam um superpoder novo — métodos com implementação padrão, conhecidos como "Default Interface Methods" (DIM).
Como isso fica no código?
public interface IAnimal
{
void SayHello();
// Método com implementação padrão!
void Walk()
{
Console.WriteLine("Eu estou andando...");
}
}
Uau! Agora a interface pode ter implementação de método. E não só um, mas quantos você quiser. Mas tem um detalhe: esses métodos precisam ser declarados com corpo, e o resto (campos, métodos privados, construtores) — ainda não pode.
3. O que as interfaces modernas conseguem
Novidades que todo dev .NET de hoje precisa conhecer:
- Métodos com implementação padrão.
- Métodos privados dentro da interface (só pra ajudar outros métodos da mesma interface).
- Métodos estáticos (desde o C# 8).
- Propriedades com implementação padrão.
- Campos estáticos (desde o C# 14 — "static interface members").
- Membros estáticos abstratos ("abstract static members" — sim, agora a interface pode exigir certos métodos estáticos na implementação!).
Exemplo de interface moderna completa:
public interface ILogger
{
static int LoggerCount { get; set; } // C# 14
void Log(string message); // Assinatura (contrato)
// Implementação padrão
void LogWarning(string warning)
{
Log("[WARNING]: " + warning);
}
// Método privado auxiliar na interface (C# 8+)
private void FormatAndLog(string level, string msg)
{
Log($"{level}: {msg}");
}
// Método estático na interface (C# 8+)
static void PrintLoggerInfo()
{
Console.WriteLine("Interface ILogger — seu melhor parceiro!");
}
}
Imagina só — antes isso era impossível, tipo deixar um gato trabalhar como segurança do servidor.
4. Classes abstratas: o que mudou?
Classes abstratas... como dizer... não evoluíram muito na última década. Elas ainda podem ter:
- Campos (inclusive privados, protegidos e estáticos).
- Métodos implementados e abstratos.
- Construtores (sim, dá pra criar classes abstratas com lógica de inicialização).
- Propriedades, eventos, indexadores.
- Membros estáticos e de instância.
Exemplo de classe abstrata:
public abstract class Animal
{
public string Name { get; set; }
public abstract void Speak();
public virtual void Walk()
{
Console.WriteLine($"{Name} está andando com as patinhas!");
}
protected void Eat()
{
Console.WriteLine($"{Name} está comendo ração.");
}
}
Classe abstrata ainda é um ótimo lugar pra guardar lógica comum, estado e comportamento pra uma hierarquia de classes.
5. Comparação moderna: tabela com as novidades
| Característica | Classe abstrata | Interface (C# 14+, .NET 9) |
|---|---|---|
| Relacionamento | is-a (é um) | can-do (sabe fazer) |
| Herança | Só uma | Múltipla |
| Campos | Sim, qualquer um | Só estáticos* (C# 14+) |
| Construtores | Sim | Não |
| Implementação de métodos | Sim (virtual/abstract) | Sim (default, static, abstract static) |
| Propriedades com implementação | Sim | Sim (implementação padrão) |
| Membros privados | Sim | Sim (só métodos, C# 8+) |
| Membros estáticos | Sim | Sim (C# 8+, com limitações) |
| Campos estáticos | Sim | Sim * (C# 14+) |
| Modificadores de acesso | Qualquer um | Padrão public ou private |
* — Em interfaces, campos estáticos são usados só em casos especiais, e é uma novidade bem recente da linguagem.
6. Quando usar interface e quando classe abstrata: dicas modernas
Interfaces (agora até com implementação padrão) — são pra criar contratos entre componentes. O lance delas: multiplicidade. Sua classe pode implementar dez interfaces diferentes, virando um verdadeiro canivete suíço.
Classe abstrata ainda é a escolha se:
- Precisa de estado comum (campos), lógica e comportamento que serão herdados por outras classes.
- Quer lógica padrão mas que pode ser sobrescrita (usa virtual).
- Quer centralizar inicialização via construtor.
Na vida real, rola muito esse esquema: "contratos puros" ficam nas interfaces, e se precisa de código ou infraestrutura comum pros filhos — cria uma classe base abstrata.
. ┌────────────────────────┐
│ Interface │
│ (contrato: o que faz) │
└─────────┬──────────────┘
│
┌──────────────┼──────────────┐
│ │ │
Implementação 1 Implementação 2 ... Implementação N
MyLogger CloudLogger FileLogger
(pode combinar com herança de classe abstrata)
7. Cenários — quando cada um ganha
Implementação múltipla:
Digamos que você tem a interface IDrivable e a classe abstrata Vehicle. Agora a classe Car pode herdar de Vehicle e ao mesmo tempo implementar várias interfaces (IDrivable, IRepairable, IInsurable). Se você tivesse uma classe abstrata Repairable, teria que escolher — Vehicle ou Repairable! Interfaces ganham fácil aqui.
Lógica e estado comum:
Digamos que todo "veículo" tem um campo "placa". Isso tem que ser campo de classe abstrata. Em interface não rola campo (pelo menos, fora os estáticos).
Evolução de API:
Uma das revoluções com Default Interface Methods — agora dá pra evoluir interfaces sem quebrar quem já usa.
Por exemplo, adicionou um método novo com implementação padrão na interface — tudo funciona, todas as implementações antigas continuam de boa! Antes isso era dor (ou, se esquecer da dor, era impossível mesmo).
8. Exemplos na prática
No nosso app de estudos, tá pintando logging. Bora criar uma interface ILogger com implementação padrão:
public interface ILogger
{
void Log(string message);
// Implementação padrão disponível pra todo mundo que implementa a interface!
void LogInfo(string info)
{
Log("[INFO] " + info);
}
// Método estático da interface
static void PrintHelp()
{
Console.WriteLine("Use ILogger pra logar eventos");
}
}
public class ConsoleLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine(message);
}
}
// Em algum lugar do código:
ILogger logger = new ConsoleLogger();
logger.LogInfo("Sistema iniciado!"); // funciona por causa da implementação padrão
// Chamando método estático da interface
ILogger.PrintHelp();
Se a gente adicionasse um método novo com implementação padrão na interface, todas as implementações existentes (tipo ConsoleLogger) iam ganhar esse método novo automaticamente — sem pânico e sem quebrar nada.
9. Erros e pegadinhas: prática e armadilhas
Nem tudo são flores, viu. Por exemplo, se sua interface tem implementação padrão, mas você acessa o objeto pelo tipo da classe, não pelo tipo da interface, a implementação padrão só tá disponível via interface.
ConsoleLogger log = new ConsoleLogger();
log.LogInfo("Hello"); // Não compila: LogInfo não tá definido na classe!
ILogger log2 = log;
log2.LogInfo("Hello"); // Suave!
Isso parece com implementação explícita de interface. Às vezes é bom pra esconder API "extra", às vezes pega iniciante de surpresa.
GO TO FULL VERSION