Start Debugging

Lösung: CS9035 "Required member 'X' must be set in the object initializer" in C#

CS9035 bedeutet, dass ein als required markiertes Member nicht zugewiesen wurde. Weisen Sie es im Objektinitialisierer zu, oder fügen Sie einen mit [SetsRequiredMembers] annotierten Konstruktor hinzu, der jedes required-Member setzt.

CS9035 tritt zur Kompilierzeit auf, wenn Sie eine Instanz eines Typs erzeugen, der ein required-Member hat, dieses Member aber nicht zugewiesen wird. Der Compiler verlangt, dass jedes required-Feld und jede required-Eigenschaft gesetzt wird, bevor das Objekt die Konstruktion verlässt. Beheben Sie es auf direktem Weg: Fügen Sie das Member dem Objektinitialisierer hinzu, zum Beispiel new Person { Name = "Ada" }. Wenn ein Konstruktor das Member bereits zuweist, teilen Sie es dem Compiler mit, indem Sie [SetsRequiredMembers] an diesen Konstruktor schreiben, damit er den Initialisierer nicht mehr verlangt. Dies ist gegen C# 14 auf .NET 11 verifiziert; der required-Modifizierer und dieses Diagnosemeldung verhalten sich seit C# 11 auf .NET 7 gleich.

Der Fehler im Kontext

Die vollständige Meldung nennt das genaue Member, das dem Compiler fehlt:

error CS9035: Required member 'Person.Name' must be set in the object initializer or attribute constructor.

Sie sehen ein CS9035 pro nicht zugewiesenem required-Member, sodass ein Typ mit drei required-Eigenschaften und einem leeren new Person() drei Fehler auf einmal erzeugt. Es ist eine Kompilierzeit-Diagnose, keine Laufzeitausnahme: Der Build schlägt fehl, nichts läuft. Das ist der ganze Sinn von required: Die Prüfung wanderte von einer NullReferenceException, auf die Sie in der Produktion trafen, zu einer roten Unterschlängelung, die Sie im Editor sehen.

Warum das passiert

Der required-Modifizierer, in C# 11 eingeführt, markiert ein Feld oder eine Eigenschaft als zwingend zum Zeitpunkt der Initialisierung. Wenn ein beliebiger Ausdruck eine neue Instanz des Typs konstruiert, prüft der Compiler, dass jedes required-Member zugewiesen ist, entweder über einen Objektinitialisierer oder über einen Konstruktor, der verspricht, sie zu setzen. Kann er das nicht beweisen, gibt er CS9035 aus.

Das Schlüsselwort ist “beweisen”. Der Compiler führt Ihren Code nicht aus. Er vertraut nur zwei Dingen: einem Member, das Sie direkt im Objektinitialisierer zuweisen, und einem Konstruktor, der explizit mit [SetsRequiredMembers] annotiert ist. Ein Konstruktor, der das Member zufällig in seinem Rumpf zuweist, aber das Attribut nicht trägt, zählt gar nichts; der Compiler liest den Rumpf nicht, um das herauszufinden. Deshalb überlebt der Fehler, selbst wenn Ihr Konstruktor den Wert eindeutig setzt: Sie müssen es dem Compiler sagen, nicht zeigen.

Drei Situationen erzeugen den Fehler:

Minimale Reproduktion

Der kleinste Typ, der CS9035 auslöst:

// .NET 11, C# 14
public class Person
{
    public required string Name { get; init; }
    public required string Email { get; init; }
    public int Age { get; init; }   // not required, optional
}

Jede dieser Konstruktionen schlägt beim Kompilieren fehl:

// .NET 11, C# 14
var a = new Person();                        // CS9035 x2 (Name and Email)
var b = new Person { Name = "Ada" };         // CS9035 x1 (Email still missing)
var c = new Person { Age = 36 };             // CS9035 x2 (Name and Email)

Nur wenn beide required-Member vorhanden sind, kompiliert sie:

// .NET 11, C# 14 -- compiles
var ok = new Person { Name = "Ada", Email = "ada@example.com" };

Beachten Sie, dass Age weggelassen wird und das in Ordnung ist. required gilt pro Member; optionale Member bleiben optional. Dem Compiler ist nur wichtig, dass die Member, die den required-Modifizierer tragen, zugewiesen sind.

Die Lösung im Detail

Arbeiten Sie diese Optionen der Reihe nach durch. Die erste ist in der großen Mehrheit der Fälle die Antwort; der Rest deckt die Situationen ab, in denen ein Initialisierer nicht das ist, was Sie wollen.

1. Weisen Sie die required-Member im Objektinitialisierer zu

Die vorgesehene Lösung ist, jedes required-Member an der Aufrufstelle zu setzen:

// .NET 11, C# 14
var person = new Person
{
    Name = "Ada Lovelace",
    Email = "ada@example.com",
    // Age is optional, omit it freely
};

Dafür ist required da: Der Vertrag des Typs sagt, dass diese Felder zwingend sind, und der Initialisierer hält sich daran. Wenn Sie feststellen, dass Sie dieselben Werte wiederholen, ist das ein Signal, dass der Typ einen Konstruktor will, was die nächste Lösung ist.

2. Fügen Sie einen mit [SetsRequiredMembers] annotierten Konstruktor hinzu

Wenn ein Konstruktor die Werte bereits entgegennimmt und zuweist, dekorieren Sie ihn mit System.Diagnostics.CodeAnalysis.SetsRequiredMembers. Dieses Attribut versichert dem Compiler, dass der Konstruktor jedes required-Member initialisiert, sodass Aufrufer keinen Objektinitialisierer mehr brauchen:

// .NET 11, C# 14
using System.Diagnostics.CodeAnalysis;

public class Person
{
    public required string Name { get; init; }
    public required string Email { get; init; }
    public int Age { get; init; }

    [SetsRequiredMembers]
    public Person(string name, string email)
    {
        Name = name;
        Email = email;
    }
}

// now this compiles, no initializer needed
var person = new Person("Ada", "ada@example.com");

Eine heikle Kante: [SetsRequiredMembers] ist eine Zusicherung, keine verifizierte Garantie. Der Compiler nimmt Sie beim Wort und prüft nicht, dass der Konstruktor tatsächlich jedes required-Member zuweist. Fügen Sie das Attribut hinzu, vergessen aber, Email im Rumpf zu setzen, erhalten Sie kein CS9035 und keine Warnung, nur ein null dort, wo Sie einen Wert versprochen haben. Halten Sie das Attribut ehrlich.

Wenn Sie einen parameterlosen Konstruktor neben dem annotierten behalten, brauchen Aufrufer der parameterlosen Version weiterhin den Initialisierer. Das Attribut befreit nur den spezifischen Konstruktor, an dem es sitzt.

3. Entfernen Sie required, wenn das Member nicht wirklich zwingend ist

Wenn das Member einen sinnvollen Standardwert hat oder wirklich optional ist, sollte es gar nicht erst required sein. Das Entfernen des Modifizierers entfernt die Verpflichtung:

// .NET 11, C# 14
public class Person
{
    public required string Name { get; init; }
    public string Email { get; init; } = "";   // was required, now optional with a default
}

Dies ist überraschend oft die richtige Entscheidung. Greifen Sie nur dann zu required, wenn es keinen vernünftigen Standardwert gibt und eine Konstruktion ohne den Wert das Objekt in einem ungültigen Zustand ließe. Einer Eigenschaft einen Standardwert zu geben und required zu entfernen, ist sauberer, als jede Aufrufstelle zu zwingen, einen leeren String zu übergeben.

4. Verwenden Sie für Records einen positionellen oder [SetsRequiredMembers]-Konstruktor

Ein positioneller Record erzeugt eine init-Eigenschaft pro Parameter, aber diese sind standardmäßig nicht required. Wenn Sie einem Record mit einem primären Konstruktor explizit eine required-Eigenschaft hinzufügen, erfüllt der primäre Konstruktor sie nicht automatisch, und Sie erhalten CS9035, wenn Sie die positionelle Form verwenden. Das bringt Leute durcheinander, weil es aussieht, als sollte der Konstruktor zählen. Die Designdiskussion in dotnet/csharplang #6780 erläutert die Begründung. Die Lösung ist, die Eigenschaft in einem Initialisierer zusätzlich zum positionellen Aufruf zu setzen, oder [SetsRequiredMembers] zu einem Konstruktor hinzuzufügen, der sie zuweist:

// .NET 11, C# 14
using System.Diagnostics.CodeAnalysis;

public record Product(string Sku)
{
    public required string Name { get; init; }

    [SetsRequiredMembers]
    public Product(string sku, string name) : this(sku) => Name = name;
}

// both work now
var withInit = new Product("A-100") { Name = "Widget" };
var withCtor = new Product("A-100", "Widget");

Wenn Sie wollen, dass die gesamte Konstruktion des Records über die positionellen Parameter erzwungen wird, bevorzugen Sie einfache init-Eigenschaften gegenüber required-Eigenschaften. Positionelle Records und required-Member zu mischen ist eine Quelle der Verwirrung; entscheiden Sie, welches Modell der Typ verwendet. Eine breitere Betrachtung, wann sich ein Record lohnt, finden Sie in record vs class vs struct in C#.

Fallstricke und Varianten

Eine Handvoll Situationen erzeugt CS9035 oder etwas, das ihm ähnelt, aus Gründen, die aus der Meldung nicht offensichtlich sind:

Das mentale Modell, das Sie behalten sollten: required verschiebt eine “Sie müssen dies bereitstellen”-Regel von einer Laufzeithoffnung zu einer Kompilierzeitgarantie, und CS9035 ist diese Garantie bei der Arbeit. Wenn Sie es sehen, ist die Lösung immer eine von “stellen Sie den Wert an der Aufrufstelle bereit” oder “teilen Sie dem Compiler mit, dass ein Konstruktor ihn bereits bereitstellt”. Entscheiden Sie, welche für den Typ zutrifft, füllen Sie dann den Initialisierer aus oder fügen Sie [SetsRequiredMembers] hinzu, und greifen Sie nie zu dem Attribut als Weg, den Fehler zum Schweigen zu bringen, ohne das Member tatsächlich zu setzen.

Verwandt

Quellen

Comments

Sign in with GitHub to comment. Reactions and replies thread back to the comments repo.

< Zurück