Inkapsling
Inkapsling handlar om att skydda ett objekts data så att bara klassen själv kan bestämma vad som händer med den. Det är en av grundpelarna i objektorienterad programmering.
Vad händer utan inkapsling?
Tänk dig ett bankkonto utan lås på dörren:
// Utan inkapsling — alla kan göra vad de villclass BankAccount{ public double balance = 1000; // publikt fält — farligt!}
// Vem som helst kan sätta saldot till vad som helstBankAccount account = new BankAccount();account.balance = -99999; // ingen koll, ingen valideringIngen kontroll. Inga spärrar. Vad som helst kan hända med dina data. Det kallas ett publikt fält — och det är i princip alltid fel.
Lösningen: stäng dörren med private
class BankAccount{ private double balance; // privat — ingen utifrån kan röra det}Fältet balance existerar fortfarande — men nu är det inlåst. Ingen kod utanför klassen kan läsa eller skriva direkt till det. Klassen själv bestämmer vad som får hända med sin data.
private och public — vad är vad?
| Nyckelord | Vem ser det? | Används till |
|---|---|---|
private | Bara klassen själv | Fält, intern logik |
public | Alla utifrån | Konstruktorer, metoder, properties |
class BankAccount{ private double balance; // bara klassen får röra det public string Owner { get; private set; } // alla kan läsa, bara klassen skriver}Enkelt: privat = inuti, publik = utifrån.
Property med private set — det kontrollerade fönstret
En property är det rekommenderade sättet att exponera data på ett kontrollerat sätt.
Gammal stil (ser du i äldre kodbaser):
private double _balance;
public double Balance{ get { return _balance; } private set { _balance = value; }}Modern stil (C# 3+):
// Kortare, men gör exakt samma sakpublic double Balance { get; private set; }Utifrån kan du läsa account.Balance. Du kan inte skriva account.Balance = 999.
Konstruktor som ingångsport
class BankAccount{ public double Balance { get; private set; }
public BankAccount(double startBalance) { Balance = startBalance; }}Konstruktorn är den enda ingångsporten när objektet skapas. Inga genvägar. Inga bakdörrar. Klassen bestämmer själv hur den får skapas.
Metoder med validering
public void Deposit(double amount){ if (amount > 0) Balance += amount;}
public bool Withdraw(double amount){ if (amount > 0 && amount <= Balance) { Balance -= amount; return true; } return false;}Metoderna kontrollerar att värdet är rimligt. Ogiltiga operationer avvisas — saldot kan aldrig hamna i ett ogiltigt tillstånd.
Varför private set och inte bara set?
// Med public set — vem som helst kan skrivapublic double Balance { get; set; }account.Balance = 1000000; // inga hinder alls
// Med private set — bara klassen skriverpublic double Balance { get; private set; }account.Balance = 1000000; // kompileringsfel — stoppas direktprivate set ger dig det bästa av två världar: alla kan läsa, bara klassen kan ändra.
Hela BankAccount — alla delar på plats
class BankAccount{ public double Balance { get; private set; }
public BankAccount(double startBalance) { Balance = startBalance; }
public void Deposit(double amount) { if (amount > 0) Balance += amount; }
public bool Withdraw(double amount) { if (amount > 0 && amount <= Balance) { Balance -= amount; return true; } return false; }}Skapa ett objekt och använd det
BankAccount account = new BankAccount(1000);
Console.WriteLine($"Saldo: {account.Balance} kr"); // 1000
account.Deposit(500);Console.WriteLine($"Saldo: {account.Balance} kr"); // 1500
bool succeeded = account.Withdraw(200);Console.WriteLine($"Uttag lyckades: {succeeded}");Console.WriteLine($"Saldo: {account.Balance} kr"); // 1300
bool failed = account.Withdraw(9999);Console.WriteLine($"Uttag lyckades: {failed}"); // FalseAllt sker via metoderna. Saldot kan aldrig hamna i ett ogiltigt tillstånd.
Ditt jobb som klassdesigner
Ställ dig alltid dessa frågor:
- Behöver kod utanför klassen läsa det här? →
public get - Behöver kod utanför klassen ändra det här? →
public set(sällan rätt!) - Är det intern logik som ingen annan ska röra? →
private
Det är inte bara teknik. Det är design.
Modernare vägar till samma sak (C# 9–12)
private set är standardlösningen, men C# har fått flera verktyg de senaste versionerna som gör inkapsling ännu mer precis.
init — skrivbar en gång, aldrig igen
// private set — bara klassen kan ändra, men den KAN ändra när den villpublic double Balance { get; private set; }
// init — sätts bara vid skapandet, sen är den låst för alltid, även för klassen självpublic string Owner { get; init; }
var account = new BankAccount { Owner = "Marcus" };account.Owner = "Anna"; // kompileringsfel — init-onlyprivate set skyddar mot omvärlden men klassen själv får fortfarande ändra värdet när den vill. init går längre: värdet låses permanent efter konstruktionen, även för klassens egna metoder. Bra för data som aldrig borde ändras efter att objektet skapats — ett skapandedatum, ett kontonummer.
required — kompilatorn kollar obligatoriska fält
public class BankAccount{ public required string Owner { get; set; } // måste sättas vid skapandet}
BankAccount account = new(); // kompileringsfel: 'Owner' krävsBankAccount account = new() { Owner = "Marcus" }; // OKInnan required fick du kontrollera obligatoriska fält i konstruktorn, i körtid. Med required fångar kompilatorn misstaget innan koden ens kör.
record — när klassen bara är data
Håller klassen bara data utan egen logik, är en record ofta ett bättre val än en class:
// Klass — kräver att du skriver Equals, ToString, etc. själv om du vill ha dempublic class Transaction{ public string Type { get; set; } public decimal Amount { get; set; }}
// Record (C# 9) — jämförelse, utskrift och kopiering på köpetpublic record Transaction(string Type, decimal Amount);
var t = new Transaction("Insättning", 500);var t2 = t with { Amount = 1000 }; // kopia med ett ändrat värde, originalet orörtEn record är immutable som standard — samma idé som init, men för hela objektet på en gång. Se Records, structs och klasser för hela jämförelsen.
Obligatorisk dad-joke
Varför älskar programmerare inkapsling?
De vill inte läcka sina privata medlemmar.