Primärkonstruktor

Eintrag zuletzt aktualisiert am: 27.04.2023

Die wesentliche, schon seit .NET 8.0 Preview 2 implementierte Neuerung in C# 12.0 sind die Primärkonstruktoren für Klassen. Alte Hasen unter den C#-Entwicklern werden sich erinnern, dass dieses Sprachfeature bereits im Jahr 2014 als Prototyp für C# 6.0 verfügbar war, dann aber doch gestrichen wurde.

Nun, sechs C#-Versionen weiter, kommt Microsoft in C# 12.0 darauf zurück, auch vor dem Hintergrund der Record-Typen, die es seit C# 9.0 mit Primärkonstruktoren gibt:
public record Person(int ID, string Name, string Website = "");

Ein Primärkonstruktor ist eine Parameterliste direkt hinter dem Typnamen. In C# 12.0 ist das auch für Klassendefinitionen möglich:
public class Person(int ID, string Name, string Website = "");

Solch eine Klasse kann ohne Inhaltsbereich (also geschweifte Klammern) existieren, ist aber wertlos. Anders als bei den in C# 9.0 eingeführten Record-Typen erstellt der Primärkonstruktor -nämlich keine öffentlichen Properties in der Klasse, sondern nur private Fields. Wenn man diese Klasse mit Primärkonstruktor in einem Decompiler betrachtet, sieht man zunächst überhaupt keine Verarbeitung der Parameter im Primärkonstruktor:
public class Person
{
public Person(int ID, string Name, string Website = "")
{
}
}

Das liegt daran, dass die Primärkonstruktorparameter gar nicht verwendet werden. Wir müssen die Klasse z.B. um ToString() erweitern, siehe Listing 1. Nun sehen wir im Decompiler, dass für jeden Primärkonstruktorparameter ein privates Field angelegt wurde inklusive Zuweisung im Konstruktor (Listing 2).

Listing 1: Klasse mit Primärkonstruktor und Methode ToString(), in der alle Primärkonstruktor verwendet werden
public class Person(int ID, string Name, string Website = "")
{
public override string ToString()
{
return $"Person #{ID}: {Name} -> {Website}";
}
}

Listing 2: Decompilat von Listing 1 mit ILSpy 8.0.0.7246-preview3
public class Person
{
[CompilerGenerated]
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private int <ID>PCBackingField;

[CompilerGenerated]
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private string <Name>PCBackingField;

[CompilerGenerated]
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private string <Website>PCBackingField;

public Person(int ID, string Name, string Website = "unbekannt")
{
<ID>PCBackingField = ID;
<Name>PCBackingField = Name;
<Website>PCBackingField = Website;
base..ctor();
}

public override string ToString()
{
return $"Person #{<ID>PC_BackingField}: {<Name>PC__BackingField} -> {<Website>PC_BackingField}";
}
}

Um öffentlich auf die im Primärkonstruktor übergebenen Daten zugreifen zu können, muss man die Konstruktorparameter für Zuweisungen verwenden, siehe Name und Website im Listing 3. In Listing 3 gibt es neben der Klasse Person eine zweite, abgeleitete Klasse Autor mit Primärkonstruktor.

Listing 3: Primärkonstruktorbeispiel mit Zuweisung der Primärkonstruktorparameter an öffentliche Properties und Vererbung
using System.ComponentModel.DataAnnotations;
using ITVisions;

namespace NET8Konsole.CS12;

/// <summary>
/// Klasse mit Primärkonstruktor
/// </summary>
public class Person(Guid personGuid, string name)
{
public Guid PersonGuid { get; set; } = personGuid;

public string Name { get; set; } = name;

public Person() : this(Guid.Empty, "")
{
// Aufruf ohne Parameter möglich, führt aber zu einem ungültigen Objekt ;-)
}

public override string ToString()
{
return $"Person {personGuid}: {Name}";
}
}

/// <summary>
/// Abgeleitete Klasse mit Primärkonstruktor
/// </summary>
public class Autor(Guid personGuid, string name, string website) : Person(personGuid, name)
{
public string Website { get; set; } = website;

public override string ToString()
{
return $"Autor {personGuid}: {Name} -> {Website}";
}
}

internal class CS12PrimaryConstructorsDemo
{
public void Run()
{
var p = new Person();
Console.WriteLine(p.Name);
Console.WriteLine(p.ToString());

var a = new Autor(Guid.NewGuid(), "Dr. Holger Schwichtenberg", "www.IT-Visions.de");
Console.WriteLine(a.Name);
Console.WriteLine(a.Website);
Console.WriteLine(a.ToString());

}
}