Properties
En property är ett kontrollerat fönster in till ett objekts data. Istället för att exponera ett fält direkt kan du styra vad som får läsas och skrivas.
När du läst detta ska du kunna
- Förklara varför vi gick från Get/Set-metoder till properties
- Förklara skillnaden mellan ett fält och en property
- Skriva auto-properties med get/set
- Begränsa skrivåtkomst med
private set - Använda
initför egenskaper som bara sätts vid skapandet - Skriva beräknade properties (expression-bodied)
- Använda
required(C# 11)
Historik — hur det såg ut förr
Innan C# hade properties fick man skriva Get- och Set-metoder för hand — precis som i Java.
public class Person{ private string _name; // backing field
public string GetName() { return _name; }
public void SetName(string name) { _name = name; }}
// Användningvar p = new Person();p.SetName("Marcus");Console.WriteLine(p.GetName()); // MarcusDet fungerar — men det är ordigt, och felstavningar i metodnamnet (GetNamnn) ger inget kompileringsfel.
C# löste det med properties: samma kontroll, halva koden.
Fält vs property
// Fält — direkt exponering, ingen kontrollpublic class Product{ public double Price; // farligt — vem som helst kan sätta -99}
// Property — kontrollerad åtkomstpublic class Product{ public double Price { get; private set; } // alla läser, bara klassen skriver}Auto-property
Den enklaste formen — C# genererar det underliggande fältet automatiskt.
public class Product{ public string Name { get; set; } // läs och skriv utifrån public double Price { get; private set; } // skriv bara inifrån klassen public int Count { get; set; }}Full property med backing field
Ibland behövs extra logik vid get eller set — t.ex. validering.
public class BankAccount{ private double _balance; // backing field
public double Balance { get { return _balance; } set { if (value < 0) throw new ArgumentException("Saldo kan inte vara negativt"); _balance = value; } }}Expression-bodied property
När logiken är ett enkelt uttryck kan du använda => för att korta ner koden.
public class Rectangle{ public double Width { get; set; } public double Height { get; set; }
// Beräknad property — inget backing field public double Area => Width * Height; public double Perimeter => 2 * (Width + Height);}
var r = new Rectangle { Width = 5, Height = 3 };Console.WriteLine(r.Area); // 15Console.WriteLine(r.Perimeter); // 16init — sätt bara vid skapandet (C# 9) ✨
Med init kan en property sättas i object initializer men inte ändras efteråt.
public class Person{ public string Name { get; init; } public int Age { get; init; }}
// Sätts en gång vid skapandetvar p = new Person { Name = "Anna", Age = 30 };
// p.Namn = "Björn"; // kompileringsfel — init tillåter inte ändring✨ C# 9 —
init: Somsetmen bara tillåtet i object initializer och konstruktor. Ger oföränderlighet utan att du behöver skriva en konstruktor med alla parametrar.
required — tvinga initiering (C# 11) ✨
required markerar att en property måste sättas när objektet skapas. Kompilatorn varnar om du glömmer den.
public class Product{ public required string Name { get; set; } // måste anges public double Price { get; set; } // valfri}
var p1 = new Product { Name = "Kaffebryggare" }; // OKvar p2 = new Product { Name = "Kaffebryggare", Price = 499.0 }; // OK// var p3 = new Produkt { Pris = 499.0 }; // kompileringsfel✨ C# 11 —
required: Ersätter mönstret att kasta i konstruktorn om ett fält saknas. Felet syns redan vid kompilering.
Kombinera init + required
public class Address{ public required string Street { get; init; } public required string City { get; init; } public string Land { get; init; } = "Sverige";}
var a = new Address { Street = "Storgatan 1", City = "Göteborg" };Console.WriteLine($"{a.Street}, {a.City}, {a.Land}");// Storgatan 1, Göteborg, Sverigeinit vs readonly — vad är skillnaden?
Båda ger oföränderlighet, men på olika sätt:
class MedReadonly{ private readonly string _name;
public MedReadonly(string name) { _name = name; // readonly — sätts bara i konstruktor }}
class MedInit{ public string Name { get; init; } // init — sätts i konstruktor ELLER object initializer}
// Med readonly måste du ha en konstruktor med parametrarvar a = new MedReadonly("Marcus");
// Med init kan du använda object initializervar b = new MedInit { Name = "Marcus" };readonly (fält) | { get; } (property) | { get; init; } (C# 9) | |
|---|---|---|---|
| Sätts i konstruktor | ✓ | ✓ | ✓ |
| Sätts i object initializer | ✗ | ✗ | ✓ |
| Sätts efter skapande | ✗ | ✗ | ✗ |
| Kan ha validering | Via konstruktor | Via konstruktor | Via konstruktor |
init är flexiblare — du behöver inte en konstruktor med alla parametrar för att skapa oföränderliga objekt.
field — slipp backing-fältet (C# 14)
Med field kan du skriva validering i get/set utan att deklarera ett separat backing-fält. Kompilatorn genererar det åt dig — precis som med en auto-property, men med plats för din logik.
Innan field:
public class Person{ private string _name; // backing field — bara för att get/set behöver den
public string Name { get => _name; set => _name = value?.Trim() ?? throw new ArgumentNullException(nameof(value)); }}Med field:
public class Person{ public string Name { get => field; set => field = value?.Trim() ?? throw new ArgumentNullException(nameof(value)); }}Resultatet är identiskt — field refererar till det dolda backing-fältet.
Fler exempel
public class Produkt{ // Trimmar namn automatiskt vid tilldelning public string Namn { get => field; set => field = value?.Trim() ?? ""; }
// Tillåter bara positiva priser public decimal Pris { get => field; set => field = value >= 0 ? value : throw new ArgumentOutOfRangeException(nameof(value), "Pris kan inte vara negativt"); }
// Loggar när värdet ändras public int Antal { get => field; set { Console.WriteLine($"Antal ändras: {field} → {value}"); field = value; } }}var p = new Produkt();p.Namn = " Kaffebryggare ";p.Pris = 499.0m;p.Antal = 10;
Console.WriteLine(p.Namn); // "Kaffebryggare" (trimmad)Output
Antal ändras: 0 → 10KaffebryggareKombinera med init
field fungerar med alla accessor-kombinationer:
public class Config{ // init med validering — ingen separat backing field public string Host { get => field; init => field = string.IsNullOrWhiteSpace(value) ? throw new ArgumentException("Host krävs") : value.ToLower(); }}TL;DR
| Variant | Syntax | När |
|---|---|---|
| Auto-property | public T Prop { get; set; } | Standard |
| Read-only utifrån | { get; private set; } | Klassen skriver, alla läser |
| Beräknad | public T Prop => expression; | Värdet räknas ut |
| init | { get; init; } | Sätt vid skapande, sedan oföränderlig |
| required | public required T Prop { get; set; } | Tvinga initiering |
| Full property | get { } set { } | Behöver validering + eget backing field |
| field | get => field; set => field = ...; | Validering utan att deklarera backing field |