C# Zugriffsmodifizierer: Türen abschließen oder offen lassen?
Zugriffsmodifizierer: Glauben Sie nicht denen, die sagen „Mach einfach alles public“
Die Lieblingslösung von Einsteigern: alles public machen. Es funktioniert, es gibt keine Fehler, und jeder kommt an alles heran. Großartig, oder?
Nein. Ganz und gar nicht.
Eines Tages setzt eine Kollegin, die Ihren Code nutzt, den Saldo direkt auf -1000. Oder jemand weist dem Altersfeld 500 zu. Oder das Passwortfeld landet in einer Logdatei.
Genau dafür gibt es Zugriffsmodifizierer – um dieses Chaos zu verhindern.
Analogie aus dem Alltag: Ihr Zuhause
Denken Sie an Ihre Wohnung:
- public: Ihre Tür – jeder sieht sie und kann eintreten (sehr gefährlich!)
- private: Ihr Schlafzimmer – nur Sie dürfen hinein
- protected: Das Wohnzimmer – nur Familienmitglieder dürfen hinein
- internal: Der Gemeinschaftsbereich im Haus – nur Bewohner haben Zugang
Übertragen wir das nun auf Code.
public: für alle offen
public bedeutet „jeder darf zugreifen“ – von außerhalb der Klasse, aus dem Projekt, von überall.
public class User
{
public string Name { get; set; }
public string Password { get; set; } // ⚠️ DANGER!
}
// Somewhere else...
User user = new User();
user.Password = "1234"; // Direct access - Bad!
Console.WriteLine(user.Password); // Password readable - Very bad!Weil password public ist, kann es jeder lesen und ändern. Das ist eine ernste Sicherheitslücke.
private: nur die Eigentümerin hat Zugriff
Auf private-Mitglieder kann nur innerhalb der Klasse zugegriffen werden, in der sie definiert sind. Von außen sieht und ändert sie niemand.
public class BankAccount
{
public string AccountOwner { get; set; }
private double _balance; // Only this class can access
private string _pin; // Must never leak outside
public BankAccount(string owner, double initialBalance, string pin)
{
AccountOwner = owner;
_balance = initialBalance;
_pin = pin;
}
public void Deposit(double amount)
{
if (amount > 0)
{
_balance += amount;
Console.WriteLine($"✅ ${amount:F2} deposited. New balance: ${_balance:F2}");
}
}
public bool Withdraw(double amount, string enteredPin)
{
if (enteredPin != _pin)
{
Console.WriteLine("❌ Wrong PIN!");
return false;
}
if (amount > _balance)
{
Console.WriteLine("❌ Insufficient balance!");
return false;
}
_balance -= amount;
Console.WriteLine($"✅ ${amount:F2} withdrawn. Remaining balance: ${_balance:F2}");
return true;
}
public double GetBalance()
{
return _balance; // We allow reading, not modifying
}
}Verwendung:
BankAccount account = new BankAccount("John", 5000, "1234");
account.Deposit(1000);
// ✅ $1000.00 deposited. New balance: $6000.00
account.Withdraw(500, "1234");
// ✅ $500.00 withdrawn. Remaining balance: $5500.00
account.Withdraw(500, "4321");
// ❌ Wrong PIN!
// These DON'T WORK:
// account._balance = 1000000; // ❌ Can't access private
// Console.WriteLine(account._pin); // ❌ Can't access privateDen Saldo können Sie nicht direkt ändern. Sie müssen Methoden nutzen, um Geld einzuzahlen oder abzuheben – und diese Methoden führen die nötigen Prüfungen durch.
protected: Familienmitglieder haben Zugriff
protected bedeutet: zugänglich innerhalb der Klasse und aus Klassen, die von ihr erben.
public class LivingBeing
{
public string Name { get; set; }
protected int _health; // Only LivingBeing and derivatives can access
public LivingBeing(string name, int health)
{
Name = name;
_health = health;
}
public void ShowStatus()
{
Console.WriteLine($"{Name}: Health {_health}");
}
}
public class Player : LivingBeing
{
public int Score { get; set; }
public Player(string name) : base(name, 100)
{
Score = 0;
}
public void TakeDamage(int amount)
{
_health -= amount; // ✅ Can access protected (derived class)
Console.WriteLine($"💥 {Name} took {amount} damage! Remaining health: {_health}");
if (_health <= 0)
{
Console.WriteLine($"☠️ {Name} died!");
}
}
public void PickUpHealthPack()
{
_health += 25; // ✅ Can access protected
if (_health > 100) _health = 100;
Console.WriteLine($"💚 {Name} picked up health pack! Health: {_health}");
}
}
// Usage
Player player = new Player("John");
player.ShowStatus(); // John: Health 100
player.TakeDamage(30); // 💥 John took 30 damage! Remaining health: 70
player.TakeDamage(50); // 💥 John took 50 damage! Remaining health: 20
player.PickUpHealthPack(); // 💚 John picked up health pack! Health: 45
// This DOESN'T WORK:
// player._health = 9999; // ❌ protected not accessible from outsideAuf das Feld _health konnten wir innerhalb der Klasse Player zugreifen, weil sie von LivingBeing erbt. Von außen ist der Zugriff jedoch nicht möglich.
internal: Zugriff auf Projektebene
Auf internal-Mitglieder kann nur innerhalb desselben Projekts (Assembly) zugegriffen werden. Aus anderen Projekten sind sie nicht erreichbar.
// Inside MyLibrary project
public class DataProcessor
{
internal string _connectionString = "Server=localhost;Database=App;";
internal void WriteLog(string message)
{
Console.WriteLine($"[LOG] {message}");
}
public void SaveData(string data)
{
WriteLog($"Saving data: {data}"); // ✅ Access from same project OK
// Save operations...
}
}
// Another class in the same project
public class ReportGenerator
{
private DataProcessor _processor = new DataProcessor();
public void GenerateReport()
{
_processor.WriteLog("Generating report..."); // ✅ internal access OK
}
}Aus einem anderen Projekt dagegen:
// In a different project
DataProcessor processor = new DataProcessor();
processor.WriteLog("Test"); // ❌ Can't access internal from outside
processor.SaveData("Data"); // ✅ public method, accessibleGetter und Setter: kontrollierter Zugriff
Sie können Getter und Setter einer Eigenschaft unterschiedliche Zugriffsstufen geben:
public class Product
{
private decimal _price;
public string Name { get; set; }
// Everyone can read, only class can modify
public decimal Price
{
get { return _price; }
private set
{
if (value >= 0)
_price = value;
}
}
// Read-only property
public string PriceText => $"${_price:N2}";
// Only readable from within class
private int _stock;
public int Stock
{
get => _stock;
internal set => _stock = value; // Only same project can modify
}
public Product(string name, decimal price, int stock)
{
Name = name;
Price = price; // private set works (we're inside the class)
_stock = stock;
}
public void ApplyDiscount(int percent)
{
Price = _price * (100 - percent) / 100; // private set works
Console.WriteLine($"🏷️ {percent}% discount applied! New price: {PriceText}");
}
}Verwendung:
Product laptop = new Product("MacBook Pro", 2499, 10);
Console.WriteLine(laptop.Price); // ✅ Read: 2499
Console.WriteLine(laptop.PriceText); // ✅ $2,499.00
// laptop.Price = 100; // ❌ ERROR! private set
laptop.ApplyDiscount(10);
// 🏷️ 10% discount applied! New price: $2,249.10Praxisbeispiel: ein System zur Benutzerverwaltung
Setzen wir nun alle Zugriffsmodifizierer gemeinsam ein:
public class User
{
// Public: Everyone can see
public int Id { get; private set; }
public string Username { get; private set; }
public string Email { get; private set; }
public DateTime RegistrationDate { get; private set; }
// Private: Only this class
private string _passwordHash;
private int _failedLoginAttempts;
private const int MAX_FAILED_ATTEMPTS = 3;
// Protected: This class and derivatives
protected bool _isActive;
protected DateTime? _lastLoginDate;
// Internal: Within project
internal string _sessionToken;
public User(int id, string username, string email, string password)
{
Id = id;
Username = username;
Email = email;
_passwordHash = HashPassword(password);
RegistrationDate = DateTime.Now;
_isActive = true;
_failedLoginAttempts = 0;
}
public bool Login(string password)
{
if (!_isActive)
{
Console.WriteLine("❌ Account is disabled!");
return false;
}
if (_failedLoginAttempts >= MAX_FAILED_ATTEMPTS)
{
Console.WriteLine("🔒 Account locked! Too many failed attempts.");
return false;
}
if (HashPassword(password) != _passwordHash)
{
_failedLoginAttempts++;
Console.WriteLine($"❌ Wrong password! ({_failedLoginAttempts}/{MAX_FAILED_ATTEMPTS})");
return false;
}
_failedLoginAttempts = 0;
_lastLoginDate = DateTime.Now;
_sessionToken = Guid.NewGuid().ToString();
Console.WriteLine($"✅ Welcome, {Username}!");
return true;
}
public bool ChangePassword(string oldPassword, string newPassword)
{
if (HashPassword(oldPassword) != _passwordHash)
{
Console.WriteLine("❌ Current password is wrong!");
return false;
}
if (newPassword.Length < 6)
{
Console.WriteLine("❌ Password must be at least 6 characters!");
return false;
}
_passwordHash = HashPassword(newPassword);
Console.WriteLine("✅ Password changed successfully!");
return true;
}
private string HashPassword(string password)
{
// In real application, use secure hash algorithm
return Convert.ToBase64String(
System.Text.Encoding.UTF8.GetBytes(password + "salt123")
);
}
public void ShowProfile()
{
Console.WriteLine($"\n👤 USER PROFILE");
Console.WriteLine($"ID: {Id}");
Console.WriteLine($"Username: {Username}");
Console.WriteLine($"Email: {Email}");
Console.WriteLine($"Registration: {RegistrationDate:MM/dd/yyyy}");
Console.WriteLine($"Status: {(_isActive ? "Active ✅" : "Inactive ❌")}");
Console.WriteLine($"Last Login: {_lastLoginDate?.ToString("MM/dd/yyyy HH:mm") ?? "Never logged in"}");
}
}
// Admin class - Inherits from User
public class Admin : User
{
public string Permission { get; set; }
public Admin(int id, string username, string email, string password, string permission)
: base(id, username, email, password)
{
Permission = permission;
}
public void BanUser(User user)
{
// protected field access - OK because we're a derived class
// But user._isActive = false; WON'T WORK - different instance
Console.WriteLine($"🚫 {user.Username} has been banned!");
}
public void ViewSystemLogs()
{
// internal field access - OK because we're in the same project
Console.WriteLine($"📋 Admin {Username} viewing logs...");
Console.WriteLine($"🔑 Session Token: {_sessionToken}");
}
}Verwendung:
// Regular user
User john = new User(1, "john_dev", "[email protected]", "password123");
john.Login("wrong"); // ❌ Wrong password! (1/3)
john.Login("wrong"); // ❌ Wrong password! (2/3)
john.Login("password123"); // ✅ Welcome, john_dev!
john.ShowProfile();
john.ChangePassword("password123", "newPass456");
// ✅ Password changed successfully!
// These DON'T WORK:
// john._passwordHash = "hack"; // ❌ private
// john._isActive = false; // ❌ protected
// Admin operations
Admin admin = new Admin(0, "superadmin", "[email protected]", "admin123", "FullAccess");
admin.Login("admin123");
admin.ViewSystemLogs();Übersichtstabelle
| Modifizierer | Gleiche Klasse | Abgeleitete Klasse | Gleiches Projekt | Außerhalb |
|---|---|---|---|---|
| public | ✅ | ✅ | ✅ | ✅ |
| private | ✅ | ❌ | ❌ | ❌ |
| protected | ✅ | ✅ | ❌ | ❌ |
| internal | ✅ | ❌* | ✅ | ❌ |
| protected internal | ✅ | ✅ | ✅ | ❌ |
| private protected | ✅ | ✅** | ❌ | ❌ |
*Eine abgeleitete Klasse kann auf internal zugreifen, wenn sie im selben Projekt liegt.
**Nur abgeleitete Klassen im selben Projekt haben Zugriff.
Wann verwenden Sie was?
| Situation | Empfehlung |
|---|---|
| Soll von außen erreichbar sein | public |
| Nur innerhalb der Klasse | private (betrachten Sie das als Standard) |
| Abgeleitete Klassen sollen zugreifen | protected |
| Gemeinsam im Projekt genutzt | internal |
| Sensible Daten (Passwort, Token) | private + kontrollierter Zugriff über Methoden |
Einführung in die Kapselung
Zugriffsmodifizierer sind letztlich die Bausteine eines der Grundprinzipien der OOP: der Kapselung.
Kapselung besagt:
- Verbergen Sie die Daten (
private) - Bieten Sie kontrollierten Zugriff (Methoden und Eigenschaften)
- Schützen Sie den inneren Zustand des Objekts
Im nächsten Beitrag betrachten wir die Kapselung ausführlicher.
Fazit: Denken Sie standardmäßig private
Die goldene Regel: Beginnen Sie mit private und öffnen Sie nur, was nötig ist.
// ❌ Bad - Everything public
public class Bad
{
public string password;
public int balance;
}
// ✅ Good - Minimum necessary access
public class Good
{
private string _password;
private int _balance;
public int Balance => _balance; // Read-only
public void ValidatePassword(string password) { ... }
public void UpdateBalance(int amount) { ... }
}Sauberer und sicherer Code! 🚀
Dies ist der vierte Beitrag der Reihe „OOP mit C#“. Im vorherigen Beitrag ging es um Konstruktoren, im nächsten um das Prinzip der Kapselung.