Ich sitze gerade beim Code-Review für einen Mandanten — Maschinenbauer, 180 Mitarbeitende, vier .NET-Entwickler im Team. Zwei davon haben unter drei Jahren Berufserfahrung. Wir reden über die Migration auf .NET 8, und der Senior-Dev möchte Primary Constructors einführen. "Spart so viel Boilerplate", sagt er. Stimmt. Ich frage: "Hat euer Team schon darüber geredet, was passiert, wenn jemand einen Parameter reassignt?" Stille. Dann: "Kann man das?"
Ja. Man kann. Und es kompiliert ohne Warnung.
Genau an dieser Stelle fängt mein Kommentar zu Milans Artikel an.
Was Milan sagt — und er hat nicht unrecht
Milan Jovanović ist einer der produktivsten .NET-Content-Ersteller im deutschen Sprachraum. In seinem Artikel erklärt er, warum er von klassischen Konstruktoren mit private readonly-Feldern auf Primary Constructors umgestiegen ist. Die Botschaft ist klar: Boilerplate weg, Lesbarkeit rauf. Aus einem OrderService mit acht Zeilen Konstruktorlogik wird eine einzeilige Klassendeklaration. Das ist kein syntaktischer Zucker — das ist echter Lärm, der aus dem Code verschwindet.
Er verschweigt den wichtigsten Pitfall nicht: Primary Constructor Parameter sind keine readonly-Felder. Der Compiler fängt dir keine versehentliche Reassignment ab. Wer Immutability-Garantien braucht, soll explizit auf readonly-Felder zuweisen. Milan nennt das, zeigt Beispiele, empfiehlt beides je nach Kontext. Faire, vollständige Analyse.
Aber sein Blickwinkel ist der eines erfahrenen Solo-Architekten, der Projekte allein oder mit eingespielten Senior-Teams umsetzt. Das ist nicht die Realität in einem KMU mit gemischtem Entwicklerteam.
Wie das in einem KMU wirklich landet
Das .NET-8-Voraussetzungsproblem kommt zuerst. Primary Constructors für reguläre Klassen kamen mit C# 12, das an .NET 8 gebunden ist. In meinen Mandaten läuft mindestens ein Drittel der aktiven .NET-Projekte noch auf .NET 6 LTS oder sogar Framework 4.8. Bevor du Primary Constructors im Team standardisierst, musst du wissen, ob ihr überhaupt die Runtime habt. Klingt banal. Passiert trotzdem regelmäßig, dass Teams Syntax diskutieren, die sie gar nicht einsetzen können.
Das Mutable-Parameter-Problem ist in kleinen Teams echter als in großen. Milan erklärt es korrekt: Ein Primary Constructor Parameter lässt sich zur Laufzeit überschreiben. Mit private readonly schlägt dir der Compiler sofort auf die Finger. Mit Primary Constructor schweigt er. In einem 15-Personen-Enterprise-Team mit Peer Reviews, Sonar, Roslyn-Analyzern und Senior-Aufsicht ist das beherrschbar. In einem KMU-Team, in dem manchmal der Junior als einziger gerade Zeit hat, in einer Service-Klasse zu arbeiten — weniger.
Ich habe einen echten Fall gesehen: eine NotificationService-Klasse, bei der ein Entwickler im Refactoring-Eifer den _emailClient neu zugewiesen hatte. Nicht bösartig, nur Unachtsamkeit beim Copy-Paste. Der Fehler flog erst im Staging auf, weil der Unit-Test den Mock direkt injiziert hat und die Reassignment nicht triggerte. Hätte private readonly nie compiliert.
Der Boilerplate-Vorteil ist real, aber überschätzt. In einem DI-Service mit drei Dependencies sparst du neun Zeilen. Das klingt viel, bis du dir vergegenwärtigst, dass diese neun Zeilen einmal geschrieben werden und danach nicht mehr angefasst werden müssen. Der eigentliche Engpass in KMU-Teams ist nicht Schreibgeschwindigkeit. Es ist Verständlichkeit für Leute, die den Code sechs Monate nicht gesehen haben.
Und hier ist der Punkt, den Milan nicht ansprechen muss — weil er nicht für diese Zielgruppe schreibt: Primary Constructors sind eine neue Syntax, die gelernt sein will. "Was ist das für ein Parameter da oben in der Klassendeklaration?" ist eine legitime Frage, wenn jemand C# vor einem Jahr gelernt hat und jetzt deinen Code reviewt. Das ist kein Argument dagegen — es ist ein Argument für Team-Onboarding, bevor du den Switch machst.
Meine konkrete Empfehlung
Für KMU-Teams mit .NET 8 und mehr als zwei Jahren Durchschnitts-Erfahrung: Macht es. Die Boilerplate-Ersparnis ist real, und Milan hat recht, dass die Lesbarkeit in Service-Klassen steigt. Aber legt eine Teamkonvention fest: entweder immer Primary Constructors, oder nie — Mischmasch innerhalb eines Projekts ist das Schlechteste aus beiden Welten.
Für Teams mit vielen Juniors oder heterogenem Erfahrungsstand: Wartet noch, oder führt gleichzeitig den Roslyn-Analyzer CS9124 ein, der vor Capture-Mutability warnt. Der Analyzer ist im SDK verfügbar, braucht aber explizite Konfiguration. Ohne ihn ist der Pitfall kein theoretisches Problem — er ist eine Falle, die du nicht siehst, bis der Hund beißt.
Für Domain Entities: Milans Empfehlung ist hier die richtige — Parameter zu Properties assignen, damit die Immutability auf Compiler-Ebene abgesichert ist. In diesem Anwendungsfall sind Primary Constructors tatsächlich eleganter als der klassische Ansatz, weil sie Required-Parameter auf Deklarationsebene sichtbar machen.
Eine Zahl, die hilft: Wenn dein Team mehr als drei Services-Klassen pro Woche neu anlegt, lohnt sich die Syntaxumstellung auf Projektlevel. Wenn ihr einmal im Monat eine neue Klasse schreibt und sonst Bestandscode pflegt, ist der Aufwand für Konvention und Onboarding größer als der Gewinn.
Das Wichtigste, was ich aus echten Mandaten mitgenommen habe: Jede Syntax-Entscheidung, die nicht im Team besprochen wurde, ist eine Schuld auf dem Konto "Code-Reviews, die zu lang dauern". Primary Constructors sind nicht schwer zu verstehen — aber sie müssen erklärt werden, bevor der erste Commit landet.
Der vollständige Original-Beitrag von Milan Jovanović: Why I Switched to Primary Constructors for DI in C#
Wenn du gerade mit deinem Team genau diese Frage diskutierst — Primary Constructors ja oder nein, .NET-8-Migration jetzt oder später, Analyzer-Setup zu aufwändig oder nicht — dann rede direkt mit mir darüber.
— Bernhard