Eine C# Property ist ein Klassenmitglied, das von außen wie ein Feld aussieht, intern aber über zwei Zugriffsmethoden läuft: einen get-Accessor zum Lesen und einen set-Accessor zum Zuweisen. Dadurch kannst du beim Lesen oder Schreiben eigenen Code ausführen – etwa prüfen, umrechnen oder protokollieren – ohne dass der aufrufende Code anders aussieht.
public class Person
{
public string Name { get; set; } = "";
}
var p = new Person();
p.Name = "Ada"; // ruft den set-Accessor auf
Console.WriteLine(p.Name); // ruft den get-Accessor auf
Wichtig für das Verständnis: Eine Property ist keine Variable. Sie speichert selbst nichts. Der Compiler übersetzt sie in Methoden, und der Wert liegt in einem Feld – entweder in einem, das du selbst schreibst, oder in einem, das der Compiler für dich anlegt.
get, set und value
Beide Accessoren haben eine feste Bedeutung:
getwird ausgeführt, wenn die Property gelesen wird. Er muss einen Wert vom Typ der Property zurückgeben.setwird ausgeführt, wenn der Property etwas zugewiesen wird. Der zugewiesene Wert steht darin unter dem Namenvaluezur Verfügung.
public class Product
{
private decimal _price;
public decimal Price
{
get { return _price; }
set { _price = value; } // "value" ist der zugewiesene Wert
}
}
value ist ein kontextabhängiges Schlüsselwort. Es existiert nur innerhalb von set und init und hat immer den Typ der Property. Du deklarierst es nicht selbst.
Feld oder Property?
Der Unterschied wird am Vergleich am deutlichsten:
public class ProductWithField
{
public decimal Price; // Feld: direkter Speicherplatz
}
public class ProductWithProperty
{
public decimal Price { get; set; } // Property: zwei Methoden
}
Von außen sieht die Nutzung identisch aus. Drei Dinge unterscheiden sich trotzdem:
- Erweiterbarkeit. Beim Feld kannst du später keine Prüfung einbauen, ohne die Deklaration zu ändern. Bei der Property fügst du einfach einen Accessor-Körper hinzu.
- Binärkompatibilität. Ein Feld in eine Property umzuwandeln ist eine brechende Änderung für bereits kompilierten Code, der die Bibliothek nutzt.
- Datenbindung und Serialisierung. Viele Frameworks werten standardmäßig Properties aus, nicht öffentliche Felder.
Als Faustregel: Öffentlich sichtbare Daten werden als Property veröffentlicht, Felder bleiben private.
Automatisch implementierte Properties
Wenn du im Accessor keine eigene Logik brauchst, schreibst du nur die Signatur. Den Speicherplatz legt der Compiler selbst an:
public class Person
{
public string FirstName { get; set; } = "";
public string LastName { get; set; } = "";
public int Age { get; set; }
}
Der Compiler erzeugt für jede dieser Properties ein verstecktes Backing Field, auf das du nicht direkt zugreifen kannst.
Die Zuweisung = "" ist ein Property-Initialisierer. Er ist bei nicht-nullbaren Referenztypen praktisch Pflicht: Ohne ihn wäre FirstName nach dem Konstruktor null, und der Compiler warnt bei aktivierten nullable reference types. Bei Werttypen wie int ist kein Initialisierer nötig, dort gilt der Standardwert 0.
Backing Field mit Validierung
Sobald du prüfen willst, brauchst du ein eigenes Feld und einen ausgeschriebenen Accessor:
public class Product
{
private decimal _price;
public decimal Price
{
get => _price;
set
{
if (value < 0)
{
throw new ArgumentOutOfRangeException(
nameof(value), "Der Preis darf nicht negativ sein.");
}
_price = value;
}
}
}
Die Prüfung gehört in den set-Accessor, denn nur dort kommt ein neuer Wert an. Ein Getter kann eine Zuweisung nicht verhindern – er wird beim Zuweisen gar nicht aufgerufen. Das ist ein Fehler, der in vielen Tutorials steht.
Ebenfalls ein Missverständnis: Für solche Validierung brauchst du kein PropertyInfo. Das gehört zur Reflection und dient dazu, Properties zur Laufzeit zu untersuchen. Für normale Prüfungen im Setter ist es überflüssig.
Die Varianten der Schreibbarkeit
Hier liegt die eigentliche Entscheidung beim Entwurf einer Klasse.
get; set; — frei änderbar
public string Nickname { get; set; } = "";
Lesen und Schreiben von überall. Der Normalfall für einfache Datenobjekte.
get; private set; — von außen lesbar, intern änderbar
public class Order
{
public decimal Total { get; private set; }
public void AddItem(decimal price)
{
Total += price; // innerhalb der Klasse erlaubt
}
}
Das ist die richtige Lösung, wenn ein Wert nach außen sichtbar sein soll, aber nur die Klasse selbst ihn ändern darf. Die gesamte Property private zu machen wäre falsch – dann käme auch der Lesezugriff von außen nicht mehr durch.
Get-only — nur im Konstruktor setzbar
public class Invoice
{
public Invoice(string number) => Number = number;
public string Number { get; } // kein set-Accessor
}
Eine Property ohne set kann ausschließlich im Konstruktor oder über einen Initialisierer belegt werden.
get; init; — nur bei der Objekterzeugung setzbar
public class Invoice
{
public string Number { get; init; } = "";
}
var i = new Invoice { Number = "R-2024-001" }; // erlaubt
// i.Number = "R-2024-002"; // Fehler: nur bei der Initialisierung
init gibt es seit C# 9. Der Unterschied zu get-only: Der Aufrufer darf einen Objektinitialisierer verwenden, danach ist der Wert gesperrt.
Eine Einschränkung, die oft übersehen wird: Weder get-only noch init machen ein Objekt automatisch unveränderlich. Sie schützen nur die Zuweisung der Property selbst. Zeigt sie auf einen veränderlichen Typ, bleibt dessen Inhalt änderbar:
public class Basket
{
public List<string> Items { get; init; } = new();
}
var b = new Basket();
// b.Items = new List<string>(); // gesperrt
b.Items.Add("Kaffee"); // erlaubt — die Liste selbst ist veränderlich
Berechnete Properties
Eine Property muss keinen gespeicherten Wert zurückgeben. Sie kann ihn aus anderen Daten bilden:
public class Person
{
public string FirstName { get; set; } = "";
public string LastName { get; set; } = "";
public string FullName => $"{FirstName} {LastName}";
}
Die Kurzschreibweise mit => heißt expression-bodied property und ist gleichbedeutend mit einem get-Accessor, der den Ausdruck zurückgibt. Sie hat kein Backing Field und wird bei jedem Zugriff neu ausgewertet – deshalb gehört dort nur schnelle Logik hinein.
required: Initialisierung erzwingen
Seit C# 11 kannst du den Aufrufer zwingen, eine Property zu belegen:
public class Customer
{
public required string Email { get; init; }
public string? Note { get; init; }
}
var c = new Customer { Email = "info@example.org" };
// var d = new Customer(); // Compilerfehler: Email fehlt
Beachte den Unterschied: required ist eine Compilerprüfung auf Vollständigkeit, keine Validierung. Der Compiler verlangt, dass ein Wert zugewiesen wird – welcher, prüft er nicht. Ein leerer String oder bei nullbaren Typen auch null erfüllt die Anforderung. Inhaltliche Prüfung brauchst du weiterhin selbst, im Setter oder im Konstruktor.
Wenn ein Konstruktor die erforderlichen Mitglieder bereits setzt, markierst du ihn mit [SetsRequiredMembers] aus System.Diagnostics.CodeAnalysis, sonst verlangt der Compiler zusätzlich den Objektinitialisierer.
Neu in C# 14: das Schlüsselwort field
Bisher galt: Sobald du Logik in einen Accessor schreiben wolltest, musstest du die automatische Property aufgeben und ein Backing Field von Hand anlegen. Mit C# 14 (ausgeliefert mit .NET 10) entfällt das. Das kontextabhängige Schlüsselwort field greift auf das vom Compiler erzeugte Backing Field zu:
public class Product
{
public decimal Price
{
get;
set => field = value >= 0
? value
: throw new ArgumentOutOfRangeException(nameof(value));
}
}
Du schreibst also nur den Accessor aus, der Logik braucht, und überlässt den anderen dem Compiler. Zwei Hinweise dazu: field funktioniert nur in Properties und Indexern, nicht in Event-Accessoren. Und wenn deine Klasse bereits ein Feld namens field besitzt, verdeckt das Schlüsselwort es innerhalb der Accessoren – dann hilft @field oder besser eine Umbenennung.
Solange du auf älteren Zielframeworks arbeitest, bleiben die Beispiele weiter oben mit explizitem Backing Field der richtige Weg. Sie funktionieren in jeder aktuell verbreiteten C#-Version.
Die Varianten im Überblick
| Variante | Schreibbar von | Passt für | Ab Version |
|---|---|---|---|
{ get; set; } |
überall | einfache Datenobjekte, Formularmodelle | C# 3 |
{ get; private set; } |
nur innerhalb der Klasse | abgeleitete Zustände wie Summen, Zähler | C# 2 |
{ get; } |
nur Konstruktor / Initialisierer | Werte, die zur Identität des Objekts gehören | C# 6 |
{ get; init; } |
nur bei der Objekterzeugung | Objektinitialisierer mit anschließender Sperre | C# 9 |
required ergänzend |
wie darunter gewählt | Pflichtangaben erzwingen | C# 11 |
=> Ausdruck |
gar nicht | berechnete Werte ohne Speicher | C# 6 |
mit field |
je nach Accessor | Validierung ohne eigenes Feld | C# 14 |
Häufige Fehler
Unbeabsichtigte Rekursion
Der Klassiker, und er führt zum Programmabsturz:
// FALSCH — StackOverflowException zur Laufzeit
public decimal Price
{
get => Price; // ruft sich selbst auf
set => Price = value; // ruft sich selbst auf
}
Der Accessor referenziert die Property statt des Feldes. Richtig ist _price (eigenes Backing Field) oder ab C# 14 field.
Falsches Zugriffsniveau
Ein häufiger Irrtum ist, die ganze Property private zu machen, obwohl sie von außen lesbar bleiben soll. Der Zugriffsmodifizierer gehört dann an den einzelnen Accessor: public string Name { get; private set; }. Der Modifizierer am Accessor muss dabei restriktiver sein als der der Property.
Fehlende Initialisierung
// Warnung bei aktivierten nullable reference types
public string Name { get; set; }
// besser:
public string Name { get; set; } = "";
// oder bewusst nullbar:
public string? Name { get; set; }
Teure Operationen im Getter
Wer eine Property liest, erwartet einen billigen Zugriff. Datenbankabfragen, Dateizugriffe oder Schleifen über große Mengen gehören deshalb in eine Methode mit sprechendem Namen, nicht in einen Getter. Eine Methode signalisiert dem Aufrufer, dass etwas passiert.
Nebenwirkungen im Getter
Ein Getter sollte den Zustand des Objekts nicht verändern. Zweimaliges Lesen hintereinander sollte denselben Wert liefern – sonst wird Debugging sehr unangenehm.
Häufige Fragen
Wann nehme ich eine Property und wann eine Methode?
Property, wenn es sich für den Aufrufer wie ein Datenzugriff anfühlt und schnell ist. Methode, wenn eine Berechnung spürbar dauert, Parameter nötig sind, eine Aktion ausgeführt wird oder jeder Aufruf ein anderes Ergebnis liefern kann.
Kostet eine Property Performance gegenüber einem Feld?
Bei einer automatisch implementierten Property praktisch nicht – der JIT-Compiler inlined solche trivialen Accessoren in aller Regel. Teuer wird es erst durch die Logik, die du selbst hineinschreibst.
Kann ich den Typ eines Accessors einschränken?
Nein, get und set arbeiten immer mit dem Typ der Property. Brauchst du unterschiedliche Typen zum Lesen und Schreiben, nimm zwei Mitglieder – etwa eine Property zum Lesen und eine Methode zum Setzen.
Gibt es Properties nur mit set?
Technisch ja, praktisch sind sie sehr selten und meistens ein Entwurfsfehler. Was nur geschrieben und nie gelesen wird, ist üblicherweise besser als Methodenparameter aufgehoben.
Wie verhalten sich Properties in Records?
Die Parameter eines record werden automatisch zu öffentlichen Properties mit get; init;. Du kannst sie überschreiben, indem du die Property im Körper des Records ausschreibst.
Was ist ein Indexer?
Eine Sonderform mit Parametern, die den Zugriff über eckige Klammern erlaubt – this[int index]. Die Accessoren funktionieren genauso, inklusive value im Setter.
Wenn du dich weiter in C#-Grundlagen einarbeiten willst, passen die Beiträge zu C# String Split und zur Umwandlung von String zu int thematisch daran an.
Die versionsabhängigen Angaben in diesem Beitrag stützen sich auf die offizielle Dokumentation: Properties (C#-Programmierhandbuch), init, required und field.