Uma classe abstrata é uma classe que só existe para ser derivada. Ela não pode ser instanciada e pode declarar membros abstratos: métodos e propriedades sem corpo que toda classe derivada concreta precisa implementar. Todo o resto é uma classe comum: ela pode ter campos, construtores e métodos totalmente implementados que as classes derivadas herdam.
Declarando uma classe abstrata
Marque a classe como abstract, e marque como abstract cada membro que as classes derivadas precisam fornecer, terminando-o com ponto e vírgula em vez de um corpo:
Saída:
Circle: area 7.07, perimeter 9.42
Rectangle: area 10.00, perimeter 13.00
Describe é escrito uma vez, na classe base, e chama Area e Perimeter, que nem existem em Shape. Esse é o ponto de um membro abstrato: a classe base pode contar com ele, e o compilador garante que toda forma concreta o forneça. Uma classe derivada que esquece um não compila:
class Triangle : Shape
{
public Triangle() : base("Triangle") { }
public override double Area() => 6;
}
// error CS0534: 'Triangle' does not implement inherited abstract member 'Shape.Perimeter()'
A solução é implementar Perimeter, ou marcar o próprio Triangle como abstract, se ele for uma base intermediária para outras classes.
Regras dos membros abstratos
- Um membro abstrato não tem corpo e é implicitamente virtual: as classes derivadas o implementam com
override. - Só classes abstratas podem conter membros abstratos. Colocar um em uma classe normal é um erro de compilação.
- Membros abstratos não podem ser
private(ninguém poderia sobrescrevê-los) nemstaticem uma classe. - Métodos, propriedades, indexadores e eventos podem ser abstratos. Campos e construtores não.
- Uma classe abstrata não precisa conter nenhum membro abstrato. Marcar uma classe só como
abstractimpede a instanciação, o que é útil para uma classe base que só faz sentido por meio das subclasses.
Uma propriedade abstrata declara quais acessores a classe derivada precisa fornecer:
abstract class Plan
{
public abstract decimal MonthlyPrice { get; }
public abstract string Name { get; }
}
class ProPlan : Plan
{
public override decimal MonthlyPrice => 12.99m;
public override string Name => "Pro";
}
Construtores em classes abstratas
Uma classe abstrata pode ter construtores. Eles não podem ser chamados com new, mas todo construtor derivado executa um por meio de : base(...), então é neles que o estado compartilhado é inicializado e validado. Declare-os como protected, o que diz exatamente isso: só as classes derivadas podem usar este construtor.
O padrão template method
O motivo mais comum para escrever uma classe abstrata é fixar a ordem de um algoritmo na classe base e deixar as classes derivadas preencherem os passos individuais. O método da base que executa os passos não é virtual, então nenhuma subclasse pode reordená-los ou pulá-los.
Saída:
item,qty,price
Coffee beans,2,11.50
Filter papers,1,3.20
| Item | Qty | Price |
| --- | --- | --- |
| Coffee beans | 2 | 11.50 |
| Filter papers | 1 | 3.20 |
Total: 26.20
Cada linha é uma tupla nomeada, e a saída é montada com um StringBuilder. Repare na mistura: Header e Row são abstract porque todo formato precisa decidi-los, e Footer é virtual com um padrão vazio porque a maioria dos formatos não precisa de nada ali. Os passos são protected, então o código de fora só pode chamar Export. As classes do framework usam a mesma ideia: Stream e TextWriter são classes base abstratas cujos muitos métodos de conveniência são construídos sobre poucos membros que uma classe derivada fornece (Read e Write para um stream, Write(char) para um text writer).
Classe abstrata vs interface
As duas permitem escrever código para um tipo sem conhecer a classe concreta. Elas diferem no que podem conter e em quantas uma classe pode ter:
| Classe abstrata | Interface | |
|---|---|---|
| Uma classe pode herdar ou implementar | uma | muitas |
| Campos de instância (estado) | sim | não |
| Construtores | sim | não |
| Métodos com corpo | sim | sim, desde o C# 8 (métodos padrão de interface) |
| Membros static | sim | sim, desde o C# 8; membros static abstract desde o C# 11 |
| Modificadores de acesso nos membros | qualquer um | public por padrão; outros permitidos desde o C# 8 |
| Structs podem usá-la | não | sim |
| Adicionar um membro depois | seguro se não for abstrato | quebra quem implementa, a menos que tenha um corpo padrão |
Os métodos padrão de interface (C# 8) diminuíram a distância, mas não a diferença central:
public interface ILogger
{
void Write(string message);
void Error(string message) => Write("ERROR: " + message); // default implementation: C# 8, .NET Core 3.0+
}
Um método padrão continua sem poder mexer em campos de instância, porque a interface não tem nenhum; ele só pode chamar outros membros da interface.
Uma forma prática de escolher:
- Interface quando você descreve uma capacidade que tipos sem relação podem ter (
IComparable<T>,IDisposable,IShippingProvider), quando structs devem poder participar, ou quando uma classe precisa de vários desses papéis. A maior parte do código com injeção de dependência é escrita para interfaces por esse motivo. - Classe abstrata quando os tipos derivados são de fato variações de uma mesma coisa e compartilham estado ou um algoritmo fixo, como os exportadores acima.
- As duas é comum: uma interface da qual quem chama depende, e uma classe base abstrata que a implementa e poupa quem implementa de reescrever as partes compartilhadas. A página de interfaces cobre o lado da interface em detalhe.
Erros comuns
- Tentar instanciar a classe base.
new Shape(...)é o CS0144. Instancie uma classe derivada. - Esquecer o
overridena implementação. Escreverpublic double Area()na classe derivada não implementa o membro abstrato; o compilador reporta CS0534 pela implementação ausente, mais um aviso de que o método novo oculta o herdado. - Um construtor público em uma classe abstrata. Inofensivo, mas enganoso. Use
protected. - Classes abstratas sem código compartilhado. Se a classe base só tem membros abstratos e nenhum estado, uma interface diz a mesma coisa e não gasta a única vaga de classe base.
- Chamar membros abstratos a partir do construtor da base. A implementação derivada executa antes do corpo do construtor derivado, então os campos que esse corpo atribui ainda estão em
0ounull.
Perguntas frequentes
O que é uma classe abstrata em C#?
Uma classe marcada como abstract serve só como classe base: você não pode criar uma instância dela com new (erro CS0144). Ela pode conter campos, construtores e métodos normais, mais membros abstract que não têm corpo e precisam ser implementados por toda classe derivada não abstrata.
Qual a diferença entre uma classe abstrata e uma interface em C#?
Uma classe só pode derivar de uma classe abstrata, mas pode implementar muitas interfaces. Uma classe abstrata pode guardar estado (campos de instância), construtores e membros com qualquer nível de acesso, e é o lugar natural para implementação compartilhada. Uma interface descreve uma capacidade sem estado de instância; desde o C# 8 ela pode ter corpos padrão de métodos, mas continua sem campos de instância nem construtores.
Qual a diferença entre métodos abstract e virtual em C#?
Um método virtual tem um corpo que as classes derivadas podem sobrescrever. Um método abstract não tem corpo e as classes derivadas precisam sobrescrevê-lo, senão elas mesmas ficam abstratas (erro CS0534 para uma classe concreta). Métodos abstratos só podem aparecer em classes abstratas.
Uma classe abstrata pode ter construtor em C#?
Sim. Ele executa quando uma classe derivada é construída, por meio de : base(...), e normalmente inicializa os campos compartilhados. Declare-o como protected: um construtor público em uma classe abstrata sugere que quem chama pode usá-lo, e não pode.
Por que não posso criar uma instância de uma classe abstrata?
Porque ela pode ter membros abstratos sem implementação: chamar new Shape().Area() não teria código para executar. O compilador reporta CS0144 ("Cannot create an instance of the abstract type or interface"). Crie uma instância de uma classe derivada concreta, e guarde-a em uma variável do tipo abstrato se quiser: Shape s = new Circle(2);.