Menu

Clases abstractas en C#: métodos abstractos, ejemplos y comparación con las interfaces

Qué es una clase abstracta en C#: una clase base que no puede instanciarse y que puede declarar miembros abstractos que sus clases derivadas deben implementar. Incluye métodos y propiedades abstractos, constructores, el patrón template method y cuándo elegir una interfaz en su lugar.

Esta página incluye editores ejecutables: edita, ejecuta y ve el resultado al instante.

Una clase abstracta es una clase que solo existe para que otras deriven de ella. No puede instanciarse, y puede declarar miembros abstractos: métodos y propiedades sin cuerpo que toda clase derivada concreta debe implementar. En todo lo demás es una clase normal: puede tener campos, constructores y métodos totalmente implementados que heredan las clases derivadas.

Declarar una clase abstracta

Marca la clase como abstract, y marca como abstract cada miembro que deban aportar las clases derivadas, terminándolo con un punto y coma en lugar de un cuerpo:

Salida:

Circle: area 7.07, perimeter 9.42
Rectangle: area 10.00, perimeter 13.00

Describe se escribe una sola vez, en la clase base, y llama a Area y a Perimeter, que ni siquiera existen en Shape. Esa es la idea de un miembro abstracto: la clase base puede contar con él, y el compilador garantiza que toda forma concreta lo proporciona. Una clase derivada que olvida uno no compila:

class Triangle : Shape
{
    public Triangle() : base("Triangle") { }
    public override double Area() => 6;
}
// error CS0534: 'Triangle' does not implement inherited abstract member 'Shape.Perimeter()'

La solución es implementar Perimeter, o marcar Triangle como abstract si es una base intermedia para otras clases.

Reglas de los miembros abstractos

  • Un miembro abstracto no tiene cuerpo y es implícitamente virtual: las clases derivadas lo implementan con override.
  • Solo las clases abstractas pueden contener miembros abstractos. Poner uno en una clase normal es un error de compilación.
  • Los miembros abstractos no pueden ser private (nadie podría sobrescribirlos) ni static en una clase.
  • Los métodos, las propiedades, los indexadores y los eventos pueden ser abstractos. Los campos y los constructores no.
  • Una clase abstracta no tiene por qué contener ningún miembro abstracto. Marcar una clase como abstract sin más solo impide instanciarla, lo que resulta útil para una clase base que solo tiene sentido a través de sus subclases.

Una propiedad abstracta declara qué accesores debe proporcionar la clase derivada:

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";
}

Constructores en las clases abstractas

Una clase abstracta puede tener constructores. No pueden llamarse con new, pero cada constructor derivado ejecuta uno mediante : base(...), así que son el sitio donde se inicializa y valida el estado compartido. Decláralos protected, que dice exactamente eso: solo las clases derivadas pueden usar esto.

El patrón template method

La razón más habitual para escribir una clase abstracta es fijar el orden de un algoritmo en la clase base y dejar que las clases derivadas rellenen los pasos concretos. El método base que ejecuta los pasos no es virtual, así que ninguna subclase puede reordenarlos ni saltárselos.

Salida:

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 fila es una tupla con nombre, y la salida se construye con un StringBuilder. Fíjate en la mezcla: Header y Row son abstract porque todo formato debe decidirlos, y Footer es virtual con un valor por defecto vacío porque la mayoría de los formatos no necesitan nada ahí. Los pasos son protected, así que el código externo solo puede llamar a Export. Las clases del framework usan la misma idea: Stream y TextWriter son clases base abstractas cuyos muchos métodos de conveniencia se construyen sobre unos pocos miembros que aporta una clase derivada (Read y Write en un stream, Write(char) en un text writer).

Clase abstracta frente a interfaz

Las dos permiten escribir código contra un tipo sin conocer la clase concreta. Se diferencian en lo que pueden contener y en cuántas puede tener una clase:

Clase abstractaInterfaz
Una clase puede heredar o implementarunamuchas
Campos de instancia (estado)síno
Constructoressíno
Métodos con cuerposísí, desde C# 8 (métodos de interfaz por defecto)
Miembros staticsísí, desde C# 8; miembros static abstract desde C# 11
Modificadores de acceso en los miembroscualquierapublic por defecto; otros permitidos desde C# 8
La pueden usar los structsnosí
Añadir un miembro más adelanteseguro si no es abstractorompe a los que implementan, salvo que tenga un cuerpo por defecto

Los métodos de interfaz por defecto (C# 8) redujeron la distancia, pero no la diferencia esencial:

public interface ILogger
{
    void Write(string message);
    void Error(string message) => Write("ERROR: " + message);   // default implementation: C# 8, .NET Core 3.0+
}

Un método por defecto sigue sin poder tocar campos de instancia, porque la interfaz no tiene ninguno; solo puede llamar a otros miembros de la interfaz.

Una forma práctica de elegir:

  • Interfaz cuando describes una capacidad que pueden tener tipos sin relación entre sí (IComparable<T>, IDisposable, IShippingProvider), cuando los structs deben poder participar, o cuando una clase necesita varios de esos papeles. La mayor parte del código de inyección de dependencias se escribe contra interfaces por este motivo.
  • Clase abstracta cuando los tipos derivados son de verdad variaciones de una misma cosa y comparten estado o un algoritmo fijo, como los exportadores de arriba.
  • Las dos es algo habitual: una interfaz de la que dependen los que llaman, y una clase base abstracta que la implementa y ahorra a los que implementan reescribir las partes compartidas. La página de interfaces trata en detalle el lado de las interfaces.

Errores comunes

  • Intentar instanciar la clase base. new Shape(...) es CS0144. Instancia una clase derivada.
  • Olvidar override en la implementación. Escribir public double Area() en la clase derivada no implementa el miembro abstracto; el compilador da CS0534 por la implementación que falta, más un aviso de que el método nuevo oculta al heredado.
  • Un constructor público en una clase abstracta. No hace daño pero confunde. Usa protected.
  • Clases abstractas sin código compartido. Si la clase base solo tiene miembros abstractos y ningún estado, una interfaz dice lo mismo y no gasta el único hueco de clase base.
  • Llamar a miembros abstractos desde el constructor base. La implementación derivada se ejecuta antes del cuerpo del constructor derivado, así que los campos que asigna ese cuerpo siguen siendo 0 o null.

Preguntas frecuentes

¿Qué es una clase abstracta en C#?

Una clase marcada como abstract está pensada solo como clase base: no puedes crear una instancia de ella con new (error CS0144). Puede contener campos, constructores y métodos normales, más miembros abstract que no tienen cuerpo y que toda clase derivada no abstracta debe implementar.

¿Qué diferencia hay entre una clase abstracta y una interfaz en C#?

Una clase solo puede derivar de una clase abstracta, pero puede implementar muchas interfaces. Una clase abstracta puede tener estado (campos de instancia), constructores y miembros de cualquier nivel de acceso, y es el sitio natural para la implementación compartida. Una interfaz describe una capacidad sin estado de instancia; desde C# 8 puede incluir cuerpos de métodos por defecto, pero sigue sin poder tener campos de instancia ni constructores.

¿Qué diferencia hay entre los métodos abstract y virtual en C#?

Un método virtual tiene un cuerpo que las clases derivadas pueden sobrescribir. Un método abstract no tiene cuerpo y las clases derivadas deben sobrescribirlo, o serán ellas mismas abstractas (error CS0534 en una clase concreta). Los métodos abstractos solo pueden aparecer en clases abstractas.

¿Puede una clase abstracta tener un constructor en C#?

Sí. Se ejecuta cuando se construye una clase derivada, mediante : base(...), y normalmente inicializa los campos compartidos. Decláralo protected: un constructor público en una clase abstracta sugiere que quien la usa puede llamarlo, y no puede.

¿Por qué no puedo crear una instancia de una clase abstracta?

Porque puede tener miembros abstractos sin implementación: llamar a new Shape().Area() no tendría código que ejecutar. El compilador da CS0144 ("Cannot create an instance of the abstract type or interface"). Crea una instancia de una clase derivada concreta y, si quieres, guárdala en una variable del tipo abstracto: Shape s = new Circle(2);.

Coddy programming languages illustration

Aprende a programar con Coddy

COMENZAR