Eine abstrakte Klasse ist eine Klasse, die nur existiert, um von ihr abzuleiten. Sie lässt sich nicht instanziieren und kann abstrakte Member deklarieren: Methoden und Properties ohne Rumpf, die jede konkrete abgeleitete Klasse implementieren muss. Alles andere an ihr ist eine gewöhnliche Klasse: Sie kann Felder, Konstruktoren und vollständig implementierte Methoden haben, die abgeleitete Klassen erben.
Eine abstrakte Klasse deklarieren
Markiere die Klasse als abstract und jeden Member, den abgeleitete Klassen liefern müssen, ebenfalls als abstract, abgeschlossen mit einem Semikolon statt eines Rumpfs:
Ausgabe:
Circle: area 7.07, perimeter 9.42
Rectangle: area 10.00, perimeter 13.00
Describe wird einmal geschrieben, in der Basisklasse, und ruft Area und Perimeter auf, die es in Shape gar nicht gibt. Genau darum geht es bei einem abstrakten Member: Die Basisklasse kann sich darauf verlassen, und der Compiler garantiert, dass jede konkrete Form ihn liefert. Eine abgeleitete Klasse, die einen vergisst, kompiliert nicht:
class Triangle : Shape
{
public Triangle() : base("Triangle") { }
public override double Area() => 6;
}
// error CS0534: 'Triangle' does not implement inherited abstract member 'Shape.Perimeter()'
Die Lösung ist, Perimeter zu implementieren oder Triangle selbst als abstract zu markieren, wenn es eine Zwischenbasis für weitere Klassen ist.
Regeln für abstrakte Member
- Ein abstrakter Member hat keinen Rumpf und ist implizit virtuell: Abgeleitete Klassen implementieren ihn mit
override. - Nur abstrakte Klassen dürfen abstrakte Member enthalten. Einen in eine normale Klasse zu setzen ist ein Kompilierfehler.
- Abstrakte Member können nicht
privatesein (niemand könnte sie überschreiben) und in einer Klasse nichtstatic. - Methoden, Properties, Indexer und Events können abstrakt sein. Felder und Konstruktoren nicht.
- Eine abstrakte Klasse muss keinen abstrakten Member enthalten. Eine Klasse nur als
abstractzu markieren verhindert die Instanziierung, nützlich für eine Basisklasse, die nur über ihre Unterklassen Sinn ergibt.
Eine abstrakte Property legt fest, welche Accessoren die abgeleitete Klasse liefern muss:
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";
}
Konstruktoren in abstrakten Klassen
Eine abstrakte Klasse kann Konstruktoren haben. Sie lassen sich nicht mit new aufrufen, aber jeder abgeleitete Konstruktor führt über : base(...) einen davon aus, sie sind also der Ort, an dem gemeinsamer Zustand initialisiert und validiert wird. Deklariere sie als protected, was genau das sagt: Nur abgeleitete Klassen können ihn verwenden.
Das Template-Method-Muster
Der häufigste Grund, eine abstrakte Klasse zu schreiben, ist, die Reihenfolge eines Algorithmus in der Basisklasse festzulegen und abgeleitete Klassen einzelne Schritte ausfüllen zu lassen. Die Basismethode, die die Schritte ausführt, ist nicht virtuell, keine Unterklasse kann sie also umordnen oder überspringen.
Ausgabe:
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
Jede Zeile ist ein benanntes Tupel, und die Ausgabe wird mit einem StringBuilder gebaut. Beachte die Mischung: Header und Row sind abstract, weil jedes Format sie festlegen muss, und Footer ist virtual mit leerem Standard, weil die meisten Formate dort nichts brauchen. Die Schritte sind protected, externer Code kann also nur Export aufrufen. Framework-Klassen nutzen dieselbe Idee: Stream und TextWriter sind abstrakte Basisklassen, deren viele Komfortmethoden auf wenigen Membern aufbauen, die eine abgeleitete Klasse liefert (Read und Write bei einem Stream, Write(char) bei einem TextWriter).
Abstrakte Klasse gegenüber Interface
Beide lassen dich Code gegen einen Typ schreiben, ohne die konkrete Klasse zu kennen. Sie unterscheiden sich darin, was sie enthalten können und wie viele eine Klasse haben kann:
| Abstrakte Klasse | Interface | |
|---|---|---|
| Eine Klasse kann erben oder implementieren | eine | viele |
| Instanzfelder (Zustand) | ja | nein |
| Konstruktoren | ja | nein |
| Methoden mit Rumpf | ja | ja, seit C# 8 (Standard-Interface-Methoden) |
| Statische Member | ja | ja, seit C# 8; statische abstrakte Member seit C# 11 |
| Zugriffsmodifizierer an Membern | beliebig | standardmäßig public; andere seit C# 8 erlaubt |
| Structs können es verwenden | nein | ja |
| Später einen Member hinzufügen | sicher, wenn er nicht abstrakt ist | bricht Implementierer, außer er hat einen Standardrumpf |
Standard-Interface-Methoden (C# 8) haben den Abstand verkleinert, aber nicht den Kernunterschied:
public interface ILogger
{
void Write(string message);
void Error(string message) => Write("ERROR: " + message); // default implementation: C# 8, .NET Core 3.0+
}
Eine Standardmethode kann trotzdem keine Instanzfelder anfassen, weil das Interface keine hat; sie kann nur andere Member des Interfaces aufrufen.
Ein praktischer Weg zur Entscheidung:
- Interface, wenn du eine Fähigkeit beschreibst, die voneinander unabhängige Typen haben können (
IComparable<T>,IDisposable,IShippingProvider), wenn Structs mitmachen sollen oder wenn eine Klasse mehrere solcher Rollen braucht. Der meiste Code mit Dependency Injection ist aus diesem Grund gegen Interfaces geschrieben. - Abstrakte Klasse, wenn die abgeleiteten Typen wirklich Varianten einer Sache sind und Zustand oder einen festen Algorithmus teilen, wie die Exporter oben.
- Beides ist verbreitet: ein Interface, von dem Aufrufer abhängen, und eine abstrakte Basisklasse, die es implementiert und Implementierern das Neuschreiben der gemeinsamen Teile erspart. Die Seite zu Interfaces behandelt die Seite des Interfaces ausführlich.
Häufige Fehler
- Versuchen, die Basisklasse zu instanziieren.
new Shape(...)ist CS0144. Instanziiere eine abgeleitete Klasse. overridean der Implementierung vergessen.public double Area()in der abgeleiteten Klasse zu schreiben implementiert den abstrakten Member nicht; der Compiler meldet CS0534 für die fehlende Implementierung, plus eine Warnung, dass die neue Methode die geerbte verdeckt.- Ein öffentlicher Konstruktor an einer abstrakten Klasse. Harmlos, aber irreführend. Nimm
protected. - Abstrakte Klassen ohne gemeinsamen Code. Hat die Basisklasse nur abstrakte Member und keinen Zustand, sagt ein Interface dasselbe und verbraucht nicht den einzigen Platz für eine Basisklasse.
- Abstrakte Member aus dem Basiskonstruktor aufrufen. Die abgeleitete Implementierung läuft vor dem Rumpf des abgeleiteten Konstruktors, Felder, die dieser Rumpf zuweist, sind also noch
0odernull.
Häufig gestellte Fragen
Was ist eine abstrakte Klasse in C#?
Eine als abstract markierte Klasse ist nur als Basisklasse gedacht: Du kannst mit new keine Instanz davon erzeugen (Fehler CS0144). Sie kann normale Felder, Konstruktoren und Methoden enthalten, dazu abstract-Member ohne Rumpf, die jede nicht abstrakte abgeleitete Klasse implementieren muss.
Was ist der Unterschied zwischen einer abstrakten Klasse und einem Interface in C#?
Eine Klasse kann nur von einer abstrakten Klasse ableiten, aber viele Interfaces implementieren. Eine abstrakte Klasse kann Zustand (Instanzfelder), Konstruktoren und Member jeder Zugriffsebene enthalten und ist der natürliche Ort für gemeinsame Implementierung. Ein Interface beschreibt eine Fähigkeit ohne Instanzzustand; seit C# 8 darf es Standardrümpfe für Methoden enthalten, aber weiterhin keine Instanzfelder und keine Konstruktoren.
Was ist der Unterschied zwischen abstrakten und virtuellen Methoden in C#?
Eine virtual-Methode hat einen Rumpf, den abgeleitete Klassen überschreiben dürfen. Eine abstract-Methode hat keinen Rumpf, und abgeleitete Klassen müssen sie überschreiben, sonst sind sie selbst abstrakt (Fehler CS0534 bei einer konkreten Klasse). Abstrakte Methoden können nur in abstrakten Klassen stehen.
Kann eine abstrakte Klasse in C# einen Konstruktor haben?
Ja. Er läuft, wenn eine abgeleitete Klasse konstruiert wird, über : base(...), und initialisiert typischerweise die gemeinsamen Felder. Deklariere ihn als protected: Ein öffentlicher Konstruktor an einer abstrakten Klasse suggeriert, dass Aufrufer ihn verwenden könnten, was sie nicht können.
Warum kann ich keine Instanz einer abstrakten Klasse erzeugen?
Weil sie abstrakte Member ohne Implementierung haben kann: Für new Shape().Area() gäbe es keinen Code, der laufen könnte. Der Compiler meldet CS0144 („Cannot create an instance of the abstract type or interface“). Erzeuge stattdessen eine Instanz einer konkreten abgeleiteten Klasse und halte sie, wenn du willst, in einer Variable des abstrakten Typs: Shape s = new Circle(2);.