1. Einführung
Zugriffsmodifikatoren sind wie Zäune und Schlösser in deinem Haus: Sie bestimmen, wer in welchen Raum darf und wer nur durch das Schlüsselloch schauen kann. Zur Erinnerung, in C# gibt es folgende:
| Modifikator | Wer sieht |
|---|---|
|
Alle |
|
Nur innerhalb der aktuellen Klasse |
|
Aktuelle Klasse und Nachfolger |
|
Ganzes Projekt (Assembly) |
|
Ganzes Projekt + Nachfolger |
|
Nur Nachfolger im selben Projekt |
Das Ziel von Kapselung ist es, Implementierungsdetails einer Klasse zu verstecken, damit niemand dein Objekt mit einem bösen Ausdruck wie obj.Field = 99999; kaputt machen kann, wenn du das nicht willst.
2. Klassische Fehler mit Zugriffslevel
Alles mit public öffnen
Der häufigste Fehler ist, alle Felder und Methoden einer Klasse als public zu deklarieren. Die Logik dahinter: "Vielleicht brauche ich das mal? Sollen andere doch auf meine Member zugreifen, wo sie wollen!". Das wirkt erstmal bequem, bis jemand deinem Objekt einen ungültigen Zustand gibt oder plötzlich deine internen Details so nutzt, dass du Angst bekommst, den Code zu ändern.
Beispiel für einen Fehler:
public class BankAccount
{
public string Owner;
public decimal Balance;
}
Jetzt kann jeder sowas machen:
var acc = new BankAccount();
acc.Owner = "";
acc.Balance = -1000000M; // Plötzlich eine Million Schulden!
Was ist falsch?
Du hast alle Sicherheitsprinzipien und gesunden Menschenverstand verletzt. Selbst wenn deine Bank nur Blätter oder Monopoly-Geld enthält, ein negativer Kontostand "nach Lust und Laune" ist schon seltsam.
Die Klasse in eine "structure ohne Struktur" verwandeln
Der zweite typische Fehler: Nicht nur Felder, sondern auch interne Properties und Methoden öffentlich machen, die eigentlich gar nicht von außen zugänglich sein sollten.
public class Vault
{
public string DoorPIN = "1234";
public void OpenDoor()
{
// Irgendeine Magie
}
}
Und jetzt? Jetzt kann jeder den PIN erfahren und die Tür zu deinem Tresor öffnen. Selbst wenn es nur ein "Test-Tresor" ist, vergisst nach einem Monat jemand dieses Loch – und dann gibt's Ärger.
Der richtige Ansatz:
Das Interface der Klasse sollte minimal und geschützt sein. Was andere brauchen, wird public. Was nur für dich ist, private. Wenn etwas für Nachfolger sein muss, dann protected.
3. Fehler bei der Kapselung
In .NET gibt es einen klaren Konsens: Keine Klassendaten (Zustand) sollten in öffentlichen Feldern gespeichert werden! Alles sollte privat oder zumindest in Properties gekapselt sein. Direkter Zugriff auf Felder ist klassischer schlechter Stil und eine Quelle schwer auffindbarer Fehler.
Warum Felder private sein sollten
Felder sind die Basis des internen Zustands eines Objekts. Wenn du sie öffentlich machst, verlierst du die Kontrolle darüber, wann und welche Werte dort landen. Dein Objekt wird anfällig für ungültige Werte und wenn sich das Format ändert, gehen alle öffentlichen Zugriffe kaputt.
Stattdessen:
Deklariere Felder als private und gib nach außen nur die nötigen Properties oder Methoden:
public class BankAccount
{
private decimal balance;
public decimal Balance
{
get { return balance; }
private set
{
if (value < 0)
throw new ArgumentException("Kontostand darf nicht negativ sein");
balance = value;
}
}
public BankAccount(decimal initialBalance)
{
Balance = initialBalance;
}
public void Deposit(decimal amount)
{
if (amount <= 0) throw new ArgumentException("Kann nicht mit null oder weniger aufladen!");
Balance += amount;
}
}
Grob falsch: public set für Daten, die nicht geändert werden dürfen
Manchmal braucht man nur get, aber Entwickler schreiben public { get; set; } "einfach so", weil es schneller geht. Am Ende kann jeder sowas machen:
account.Balance = 99999999; // Warum nicht?
Besser so:
Wenn eine Property nur innerhalb der Klasse gesetzt werden soll, mach den set-Teil mit Modifikator:
public decimal Balance { get; private set; }
Oder, wenn der Wert nur beim Erstellen gesetzt werden soll (C# 14):
public decimal Balance { get; init; }
4. Wenn protected auch zur Falle wird
Offene Türen für alle Nachfolger
protected ist ein guter Weg, Funktionalität an Nachfolger weiterzugeben, aber manche Daten sollten selbst Nachfolgern nicht zugänglich sein! Zum Beispiel, wenn deine Kinder (Nachfolger) kritische Felder nicht direkt ändern dürfen.
Beispiel:
public class SecureVault
{
protected string secretCode = "1234";
}
Jetzt kann jeder Nachfolger sowas machen:
public class HackerVault : SecureVault
{
public void Hack()
{
secretCode = "0000"; // Geheimcode einfach geändert!
}
}
Empfehlung:
Nutze protected nur für Dinge, die wirklich für Nachfolger gedacht sind. Alles andere bleibt privat, und für nötige Operationen schreibst du geschützte Methoden.
5. Fehler mit internal und verwirrte Assemblies
Der Modifikator internal wirkt verlockend: "Soll alles im Projekt zugänglich sein". Aber sobald das Projekt größer als eine Datei wird oder Bibliotheken dazukommen, gibt's Konflikte. Oft machen Studierende versehentlich etwas innerhalb der Assembly öffentlich, obwohl die Klasse komplett versteckt sein sollte.
Beispiel:
internal class Logger
{
internal void Write(string msg) { /* ... */ }
}
Jetzt kann jeder Logger.Write aus jeder Datei des Projekts aufrufen. Und wenn in einem Jahr jemand deine DLL in ein anderes Projekt einbindet? Die ganze "interne Küche" ist sichtbar, wenn du etwas public gelassen hast.
Tipp:
Mach internal für technische Klassen, aber halte sensible Details trotzdem private.
6. Praxis: Banking-App
Wir machen mit unserem Beispiel einer Banking-App weiter, um in der Praxis zu sehen, wie leicht man Fehler macht und was man dagegen tun kann.
Beispiel Fehler Nr. 1: Public Felder
public class BankAccount
{
public decimal Balance;
public string Owner;
}
Problem: Jeder kann das kaputt machen.
Mit Properties korrigieren
public class BankAccount
{
private decimal _balance;
private string _owner;
public string Owner => _owner; // Nur lesbar
public decimal Balance
{
get => _balance;
private set
{
if (value < 0) throw new ArgumentException("Kontostand darf nicht negativ sein");
_balance = value;
}
}
public BankAccount(string owner, decimal initialBalance)
{
if (string.IsNullOrWhiteSpace(owner))
throw new ArgumentException("Name des Besitzers ist erforderlich");
_owner = owner;
Balance = initialBalance;
}
public void Deposit(decimal amount)
{
if (amount <= 0)
throw new ArgumentException("Einzahlungsbetrag muss positiv sein");
Balance += amount;
}
}
Wenn du jetzt sowas versuchst:
var acc = new BankAccount("Lena", 1000m);
acc.Balance = -700m; // Fehler! set ist privat.
Oder sogar so:
acc.Owner = "Hacker"; // Compilerfehler, kein set
— klappt nicht. Nur über Konstruktor oder Methoden.
Fehler: Properties "nur für die Optik"
Viele schreiben:
public int Age { get; set; }
Obwohl das Alter nur über die Methode IncreaseAge geändert werden sollte (z.B. am 1. Januar), aber nicht direkt.
7. Visuelle Erinnerung: wie man es nicht und wie man es machen sollte
graph TD
A[Public Fields] -->|Any code| D[Unkontrollierter Zustand]
B[Private Fields + Public Methods] -->|Gut definiert| E[Kontrollierter Zustand]
F[Public Setters] -->|Jeder kann ändern| D
G[Private Setters] -->|Nur Klasse| E
| Ansatz | Zustand geschützt? | Kann validiert werden? | Leicht erweiterbar? |
|---|---|---|---|
| Public fields | ❌ | ❌ | ❌ |
| Properties (get/set public) | ❌ | Teilweise | Ja, aber unsicher |
| Private fields + property mit set private | ✅ | ✅ | ✅ |
8. Stil-Tipps für die Arbeit mit Kapselung
- Öffne nach außen nur das, was der Nutzer der Klasse wirklich braucht.
- Validiere immer die Daten, die ins Objekt kommen.
- Vermeide Übertreibung: Wenn eine Property nicht von außen geändert werden soll, mach den set privat.
- Für spezielle Logik – immer Methoden nutzen, nicht direkten Zugriff.
- Auch wenn es verlockend ist – mach keine public Felder "nur zum Testen".
- Nutze Properties statt direkter Felder selbst für öffentliches Lesen – das lässt sich später leicht erweitern.
GO TO FULL VERSION