İçeriğe geç / Skip to content / Zum Inhalt
Ahmet Balaman LogoAhmet Balaman

C# Zugriffsmodifizierer: Türen abschließen oder offen lassen?

Ahmet Balaman
C#OOPAccess ModifiersEncapsulation.NETProgramming Fundamentals

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 private

Den 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 outside

Auf 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, accessible

Getter 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.10

Praxisbeispiel: 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.

Kommentare