Freigeben über


Datensätze

Hinweis

Dieser Artikel ist eine Feature-Spezifikation. Die Spezifikation dient als Designdokument für das Feature. Es enthält vorgeschlagene Spezifikationsänderungen sowie Informationen, die während des Entwurfs und der Entwicklung des Features erforderlich sind. Diese Artikel werden veröffentlicht, bis die vorgeschlagenen Spezifikationsänderungen abgeschlossen und in die aktuelle ECMA-Spezifikation aufgenommen werden.

Es kann einige Abweichungen zwischen der Feature-Spezifikation und der abgeschlossenen Implementierung geben. Diese Unterschiede werden in den entsprechenden Hinweisen zum Language Design Meeting (LDM) erfasst.

Weitere Informationen zum Prozess für die Aufnahme von Funktions-Speclets in den C#-Sprachstandard finden Sie im Artikel zu den Spezifikationen.

Dieser Vorschlag verfolgt die Spezifikation für das C# 9-Datensatzmerkmal, wie es vom C#-Sprachdesignteam vereinbart wurde.

Die Syntax für einen Datensatz lautet wie folgt:

record_declaration
    : attributes? class_modifier* 'partial'? 'record' identifier type_parameter_list?
      parameter_list? record_base? type_parameter_constraints_clause* record_body
    ;

record_base
    : ':' class_type argument_list?
    | ':' interface_type_list
    | ':' class_type argument_list? ',' interface_type_list
    ;

record_body
    : '{' class_member_declaration* '}' ';'?
    | ';'
    ;

Datensatztypen sind Referenztypen, ähnlich einer Klassendeklaration. Es ist ein Fehler, wenn ein Datensatz eine record_baseargument_list bereitstellt, wenn die record_declaration keine parameter_list enthält. Höchstens eine partielle Typdeklaration eines partiellen Datensatzes darf ein parameter_list enthalten.

Datensatz-Parameter können keine ref, out oder this Modifikatoren verwenden (in und params sind jedoch zulässig).

Vererbung

Datensätze können nicht von Klassen erben, es sei denn, die Klasse ist object, und Klassen können nicht von Datensätzen erben. Datensätze können von anderen Datensätzen erben.

Mitglieder eines Datensatztyps

Zusätzlich zu den im Datensatzkörper deklarierten Mitgliedern verfügt ein Datensatztyp über zusätzliche synthetisierte Mitglieder. Mitglieder werden synthetisiert, es sei denn, ein Mitglied mit einer „übereinstimmenden” Signatur wird im Body des Datensatzes deklariert oder ein zugreifbares konkretes nicht-virtuelles Mitglied mit einer „übereinstimmenden” Signatur wird vererbt. Ein übereinstimmendes Mitglied verhindert, dass der Compiler dieses Mitglied generiert, nicht andere synthetisierte Mitglieder. Zwei Mitglieder werden als übereinstimmend betrachtet, wenn sie die gleiche Signatur haben oder in einem Vererbungsszenario als "versteckt" betrachtet werden würden. Es ist ein Fehler, wenn ein Mitglied eines Datensatzes „Clone” genannt wird. Es ist ein Fehler, wenn ein Instanzfeld eines Datensatzes einen Zeigertyp auf oberster Ebene aufweist. Ein geschachtelter Zeigertyp, z. B. ein Array von Zeigern, ist zulässig.

Die synthetisierten Mitglieder sind wie folgt:

Equality-Mitglieder

Wenn der Datensatz von object abgeleitet wird, enthält der Datensatztyp eine synthetisierte schreibgeschützte Eigenschaft, die einer wie folgt deklarierten Eigenschaft entspricht:

Type EqualityContract { get; }

Die Eigenschaft lautet private, wenn der Datensatztyp sealed ist. Andernfalls lautet die Eigenschaft virtual und protected. Die Eigenschaft kann explizit deklariert werden. Es ist ein Fehler, wenn die explizite Deklaration nicht mit der erwarteten Signatur oder Zugänglichkeit übereinstimmt, oder wenn die explizite Deklaration das Überschreiben in einem abgeleiteten Typ nicht zulässt und der Datensatztyp nicht sealed ist.

Wenn der Datensatztyp von einem Basiseintragstyp Base abgeleitet wird, enthält der Datensatztyp eine synthetisierte schreibgeschützte Eigenschaft, die einer wie folgt deklarierten Eigenschaft entspricht:

protected override Type EqualityContract { get; }

Die Eigenschaft kann explizit deklariert werden. Es ist ein Fehler, wenn die explizite Deklaration nicht mit der erwarteten Signatur oder Zugänglichkeit übereinstimmt, oder wenn die explizite Deklaration das Überschreiben in einem abgeleiteten Typ nicht zulässt und der Datensatztyp nicht sealed ist. Es ist ein Fehler, wenn eine synthetisierte oder explizit deklarierte Eigenschaft keine Eigenschaft mit dieser Signatur im Datensatztyp Base überschreibt (z. B. wenn die Eigenschaft im Base fehlt, oder versiegelt oder nicht virtuell usw.). Die synthetisierte Eigenschaft gibt typeof(R) zurück, wobei R der Datensatztyp ist.

Der Datensatztyp implementiert System.IEquatable<R> und enthält eine synthetisierte, stark typisierte Überladung von Equals(R? other), wobei R der Datensatztyp ist. Die Methode ist public, und die Methode ist virtual, es sei denn, der Datensatztyp ist sealed. Die Methode kann explizit deklariert werden. Es ist ein Fehler, wenn die explizite Deklaration nicht mit der erwarteten Signatur oder Zugänglichkeit übereinstimmt, oder die explizite Deklaration das Überschreiben in einem abgeleiteten Typ nicht zulässt und der Datensatztyp nicht sealed ist.

Wenn Equals(R? other) benutzerdefiniert (nicht synthetisiert) ist, aber GetHashCode nicht benutzerdefiniert ist, wird eine Warnung erzeugt.

public virtual bool Equals(R? other);

Der synthetisierte Equals(R?) gibt true zurück, genau dann, wenn jeder der folgenden Einträge true ist:

  • other ist ungleich null und
  • Für jedes Instanzfeld fieldN in einem Datensatztyp, der nicht vererbt wird, gilt der Wert von System.Collections.Generic.EqualityComparer<TN>.Default.Equals(fieldN, other.fieldN), wobei TN der Feldtyp ist, und
  • Wenn ein Basisdatensatztyp vorhanden ist, wird der Wert von base.Equals(other) (ein nicht virtueller Aufruf an public virtual bool Equals(Base? other)) verwendet; andernfalls der Wert von EqualityContract == other.EqualityContract.

Der Datensatztyp enthält synthetisierte ==- und !=-Operatoren, die den wie folgt deklarierten Operatoren entsprechen:

public static bool operator==(R? left, R? right)
    => (object)left == right || (left?.Equals(right) ?? false);
public static bool operator!=(R? left, R? right)
    => !(left == right);

Die Equals-Methode, die vom ==-Operator aufgerufen wird, ist die oben angegebene Equals(R? other)-Methode. Der !=-Operator delegiert an den ==-Operator. Es ist ein Fehler, wenn die Operatoren explizit deklariert werden.

Wenn der Datensatztyp von einem Basiseintragstyp Base abgeleitet wird, enthält der Datensatztyp eine synthetisierte Überschreibung, die einer wie folgt deklarierten Methode entspricht:

public sealed override bool Equals(Base? other);

Es ist ein Fehler, wenn die Überschreibung explizit deklariert wird. Es ist ein Fehler, wenn die Methode eine Methode nicht mit derselben Signatur im Datensatztyp Base überschreibt (z. B. wenn die Methode im Base fehlt, oder versiegelt oder nicht virtuell usw.). Die synthetisierte Überschreibung gibt Equals((object?)other) zurück.

Der Datensatztyp enthält eine synthetisierte Überschreibung, die einer wie folgt deklarierten Methode entspricht:

public override bool Equals(object? obj);

Es ist ein Fehler, wenn die Überschreibung explizit deklariert wird. Es ist ein Fehler, wenn die Methode object.Equals(object? obj) nicht überschreibt (z. B. aufgrund von Schatten in Zwischenbasistypen usw.). Die synthetisierte Überschreibung gibt Equals(other as R) zurück, wobei R der Datensatztyp ist.

Der Datensatztyp enthält eine synthetisierte Überschreibung, die einer wie folgt deklarierten Methode entspricht:

public override int GetHashCode();

Die Methode kann explizit deklariert werden. Es ist ein Fehler, wenn die explizite Deklaration das Überschreiben in einem abgeleiteten Typ nicht zulässt und der Datensatztyp nicht sealed ist. Es handelt sich um einen Fehler, wenn eine synthetisierte oder explizit deklarierte Methode object.GetHashCode() nicht überschreibt (z. B. aufgrund von Schatten in Zwischenbasistypen usw.).

Eine Warnung wird geliefert, wenn eine der Methoden Equals(R?) und GetHashCode() explizit deklariert ist, die andere Methode jedoch nicht explizit.

Die synthetisierte Überschreibung von GetHashCode() ergibt als int das Ergebnis der Kombination folgender Werte:

  • Für jedes Instanzfeld fieldN in einem Datensatztyp, der nicht vererbt wird, gilt der Wert von System.Collections.Generic.EqualityComparer<TN>.Default.GetHashCode(fieldN), wobei TN der Feldtyp ist, und
  • Wenn ein Basisdatensatztyp vorhanden ist, der Wert base.GetHashCode(); andernfalls der Wert System.Collections.Generic.EqualityComparer<System.Type>.Default.GetHashCode(EqualityContract).

Berücksichtigen Sie beispielsweise folgende Datensatztypen:

record R1(T1 P1);
record R2(T1 P1, T2 P2) : R1(P1);
record R3(T1 P1, T2 P2, T3 P3) : R2(P1, P2);

Bei diesen Datensatztypen würden die synthetisierten Gleichheitsmitglieder etwa wie folgt aussehen:

class R1 : IEquatable<R1>
{
    public T1 P1 { get; init; }
    protected virtual Type EqualityContract => typeof(R1);
    public override bool Equals(object? obj) => Equals(obj as R1);
    public virtual bool Equals(R1? other)
    {
        return !(other is null) &&
            EqualityContract == other.EqualityContract &&
            EqualityComparer<T1>.Default.Equals(P1, other.P1);
    }
    public static bool operator==(R1? left, R1? right)
        => (object)left == right || (left?.Equals(right) ?? false);
    public static bool operator!=(R1? left, R1? right)
        => !(left == right);
    public override int GetHashCode()
    {
        return HashCode.Combine(EqualityComparer<Type>.Default.GetHashCode(EqualityContract),
            EqualityComparer<T1>.Default.GetHashCode(P1));
    }
}

class R2 : R1, IEquatable<R2>
{
    public T2 P2 { get; init; }
    protected override Type EqualityContract => typeof(R2);
    public override bool Equals(object? obj) => Equals(obj as R2);
    public sealed override bool Equals(R1? other) => Equals((object?)other);
    public virtual bool Equals(R2? other)
    {
        return base.Equals((R1?)other) &&
            EqualityComparer<T2>.Default.Equals(P2, other.P2);
    }
    public static bool operator==(R2? left, R2? right)
        => (object)left == right || (left?.Equals(right) ?? false);
    public static bool operator!=(R2? left, R2? right)
        => !(left == right);
    public override int GetHashCode()
    {
        return HashCode.Combine(base.GetHashCode(),
            EqualityComparer<T2>.Default.GetHashCode(P2));
    }
}

class R3 : R2, IEquatable<R3>
{
    public T3 P3 { get; init; }
    protected override Type EqualityContract => typeof(R3);
    public override bool Equals(object? obj) => Equals(obj as R3);
    public sealed override bool Equals(R2? other) => Equals((object?)other);
    public virtual bool Equals(R3? other)
    {
        return base.Equals((R2?)other) &&
            EqualityComparer<T3>.Default.Equals(P3, other.P3);
    }
    public static bool operator==(R3? left, R3? right)
        => (object)left == right || (left?.Equals(right) ?? false);
    public static bool operator!=(R3? left, R3? right)
        => !(left == right);
    public override int GetHashCode()
    {
        return HashCode.Combine(base.GetHashCode(),
            EqualityComparer<T3>.Default.GetHashCode(P3));
    }
}

Methoden zum Kopieren und Klonen von Membern

Ein Datensatztyp enthält zwei kopierte Mitglieder:

  • Ein Konstruktor, der ein einzelnes Argument des Datensatztyps entgegennimmt. Sie wird als „Kopierkonstruktor” bezeichnet.
  • Eine synthetisierte öffentliche parameterlose Instanz-„Clone”-Methode mit einem compilerreservierten Namen

Der Zweck des Kopierkonstruktors besteht darin, den Zustand vom Parameter in die neue Instanz zu kopieren, die erstellt wird. Dieser Konstruktor führt keine Instanzfeld-/Eigenschaftsinitialisierer aus, die in der Datensatzdeklaration vorhanden sind. Wenn der Konstruktor nicht explizit deklariert wird, wird ein Konstruktor vom Compiler synthetisiert. Wenn der Datensatz versiegelt ist, ist der Konstruktor privat, andernfalls wird er geschützt. Ein explizit deklarierter Kopierkonstruktor muss entweder öffentlich oder geschützt sein, es sei denn, der Datensatz ist versiegelt. Als Erstes muss der Konstruktor einen Kopierkonstruktor der Basis aufrufen oder, falls der Datensatz vom Objekt erbt, einen parameterlosen Objektkonstruktor. Wenn ein benutzerdefinierter Kopierkonstruktor einen impliziten oder expliziten Konstruktorinitialisierer verwendet, der diese Anforderung nicht erfüllt, wird ein Fehler gemeldet. Nachdem ein Basiskopierkonstruktor aufgerufen wurde, kopiert ein synthetisierter Kopierkonstruktor die Werte für alle Instanzfelder, die implizit oder explizit innerhalb des Datensatztyps deklariert sind. Das alleinige Vorhandensein eines Kopierkonstruktors, ob explizit oder implizit, verhindert nicht das automatische Hinzufügen eines Standardinstanzkonstruktors.

Wenn eine virtuelle „Clone”-Methode im Basisdatensatz vorhanden ist, überschreibt die synthetisierte „Clone”-Methode sie, und der Rückgabetyp der Methode ist der aktuelle enthaltende Typ. Wenn die „Clone”-Methode für den Basisdatensatz versiegelt ist, wird ein Fehler erzeugt. Wenn eine virtuelle „Clone”-Methode nicht im Basisdatensatz vorhanden ist, ist der Rückgabetyp der „Clone”-Methode der enthaltende Typ, und die Methode ist virtuell, es sei denn, der Datensatz ist versiegelt oder abstrakt. Wenn der enthaltende Datensatz abstrakt ist, ist die synthetisierte Klonmethode ebenfalls abstrakt. Wenn die „Clone”-Methode nicht abstrahiert ist, wird das Ergebnis eines Aufrufs eines Kopierkonstruktors zurückgegeben.

Druck-Mitglieder: PrintMembers- und ToString-Methoden

Wenn der Datensatz von object abgeleitet wird, enthält der Datensatz eine synthetisierte Methode, die einer wie folgt deklarierten Methode entspricht:

bool PrintMembers(System.Text.StringBuilder builder);

Die Methode ist private, wenn der Datensatztyp sealedist. Andernfalls wird die Methode virtual und protected.

Die -Methode:

  1. ruft die Methode System.Runtime.CompilerServices.RuntimeHelpers.EnsureSufficientExecutionStack() auf, wenn die Methode vorhanden ist und der Datensatz druckbare Mitglieder aufweist.
  2. fügt für jedes Druck-Mitglied des Datensatzes (nicht statisches öffentliches Feld und lesbare Eigenschaftsmitglieder) den Namen des Mitglieds an, gefolgt von „ = ” und dem Wert des Mitglieds, getrennt durch „ , ”,
  3. gibt „true” zurück, wenn der Datensatz Druck-Mitglieder hat.

Für ein Mitglied mit einem Werttyp werden wir dessen Wert mithilfe der effizientesten, für die Zielplattform verfügbaren Methode in eine Zeichenfolgendarstellung konvertieren. Zur Zeit bedeutet das, dass Sie ToString aufrufen, bevor Sie StringBuilder.Append übergeben.

Wenn der Datensatztyp von einem Basiseintrag Base abgeleitet wird, enthält der Datensatz eine synthetisierte Überschreibung, die einer wie folgt deklarierten Methode entspricht:

protected override bool PrintMembers(StringBuilder builder);

Wenn der Datensatz keine druckbaren Elemente aufweist, ruft die Methode die Basismethode PrintMembers mit einem Argument auf (ihrem builder-Parameter) und gibt das Ergebnis zurück.

Andernfalls lautet die Methode:

  1. ruft die Base-PrintMembers-Methode mit einem Argument (dessen builder-Parameter) auf.
  2. wenn die PrintMembers-Methode „true” zurückgegeben hat, fügen Sie „ , ” an den Generator an,
  3. fügt für jedes druckbare Mitglied des Datensatzes den Namen dieses Mitglieds gefolgt von „ = ” gefolgt vom Wert des Mitglieds an: this.member (oder this.member.ToString() für Werttypen), getrennt mit „ , ”,
  4. „true” zurückgeben.

Die Methode PrintMembers kann explizit deklariert werden. Es ist ein Fehler, wenn die explizite Deklaration nicht mit der erwarteten Signatur oder Zugänglichkeit übereinstimmt, oder wenn die explizite Deklaration das Überschreiben in einem abgeleiteten Typ nicht zulässt und der Datensatztyp nicht sealed ist.

Der Datensatz enthält eine synthetisierte Methode, die einer wie folgt deklarierten Methode entspricht:

public override string ToString();

Die Methode kann explizit deklariert werden. Es ist ein Fehler, wenn die explizite Deklaration nicht mit der erwarteten Signatur oder Zugänglichkeit übereinstimmt, oder wenn die explizite Deklaration das Überschreiben in einem abgeleiteten Typ nicht zulässt und der Datensatztyp nicht sealed ist. Es handelt sich um einen Fehler, wenn eine synthetisierte oder explizit deklarierte Methode object.ToString() nicht überschreibt (z. B. aufgrund von Schatten in Zwischenbasistypen usw.).

Die synthetisierte Methode:

  1. erstellt eine StringBuilder-Instanz,
  2. fügt den Namen des Datensatzes an den Builder an, gefolgt von „ { ”,
  3. ruft die PrintMembers-Methode des Datensatzes auf und übergibt ihm den Builder, gefolgt von „ ”, wenn sie „true” zurückgibt,
  4. fügt "}" an,
  5. gibt den Inhalt des Builders mit builder.ToString() zurück.

Berücksichtigen Sie beispielsweise folgende Datensatztypen:

record R1(T1 P1);
record R2(T1 P1, T2 P2, T3 P3) : R1(P1);

Bei diesen Datensatztypen würden die synthetisierten Druck-Mitglieder etwa wie folgt aussehen:

class R1 : IEquatable<R1>
{
    public T1 P1 { get; init; }
    
    protected virtual bool PrintMembers(StringBuilder builder)
    {
        builder.Append(nameof(P1));
        builder.Append(" = ");
        builder.Append(this.P1); // or builder.Append(this.P1.ToString()); if T1 is a value type
        
        return true;
    }
    
    public override string ToString()
    {
        var builder = new StringBuilder();
        builder.Append(nameof(R1));
        builder.Append(" { ");

        if (PrintMembers(builder))
            builder.Append(" ");

        builder.Append("}");
        return builder.ToString();
    }
}

class R2 : R1, IEquatable<R2>
{
    public T2 P2 { get; init; }
    public T3 P3 { get; init; }
    
    protected override bool PrintMembers(StringBuilder builder)
    {
        if (base.PrintMembers(builder))
            builder.Append(", ");
            
        builder.Append(nameof(P2));
        builder.Append(" = ");
        builder.Append(this.P2); // or builder.Append(this.P2); if T2 is a value type
        
        builder.Append(", ");
        
        builder.Append(nameof(P3));
        builder.Append(" = ");
        builder.Append(this.P3); // or builder.Append(this.P3); if T3 is a value type
        
        return true;
    }
    
    public override string ToString()
    {
        var builder = new StringBuilder();
        builder.Append(nameof(R2));
        builder.Append(" { ");

        if (PrintMembers(builder))
            builder.Append(" ");

        builder.Append("}");
        return builder.ToString();
    }
}

Mitglieder des positionsbezogenen Datensatzes

Zusätzlich zu den oben genannten Mitgliedern synthetisieren Datensätze mit einer Parameterliste („Positionsdatensätze”) weitere Mitglieder mit den gleichen Bedingungen wie die oben genannten Mitglieder.

Primärer Konstruktor

Ein Datensatztyp verfügt über einen öffentlichen Konstruktor, dessen Signatur den Wertparametern der Typdeklaration entspricht. Dies wird als primärer Konstruktor für den Typ bezeichnet und bewirkt, dass der implizit deklarierte Standardklassenkonstruktor (sofern vorhanden) unterdrückt wird. Es ist ein Fehler, wenn ein primärer Konstruktor und ein Konstruktor mit der gleichen Signatur bereits in der Klasse vorhanden sind.

Zur Laufzeit des primären Konstruktors

  1. führt die Instanzinitialisierer aus, die im Klassenkörper angezeigt werden

  2. ruft den Basisklassenkonstruktor mit den in der record_base-Klausel angegebenen Argumenten auf, sofern diese vorhanden ist

Wenn ein Datensatz über einen primären Konstruktor verfügt, muss ein benutzerdefinierter Konstruktor, außer für den Kopierkonstruktor, über einen expliziten this-Konstruktorinitialisierer verfügen.

Parameter des Primärkonstruktors sowie die Mitglieder des Rekords befinden sich im Bereich der argument_list der record_base-Klausel und in Initialisierern von Instanzfeldern oder Eigenschaften. Instanzmitglieder wären an diesen Orten ein Fehler (ähnlich wie Instanzmitglieder heute in regulären Konstruktorinitialisierern im Bereich sind, aber ein Fehler bei der Verwendung), aber die Parameter des primären Konstruktors wären im Bereich und verwendbar und würden Mitglieder überschatten. Statische Mitglieder können auch verwendet werden, ähnlich wie Basisaufrufe und Initialisierer heute in normalen Konstruktoren funktionieren.

Eine Warnung wird erzeugt, wenn ein Parameter des primären Konstruktors nicht gelesen wird.

Ausdrucksvariablen, die im argument_list deklariert sind, sind im Geltungsbereich von argument_list. Dieselben Schattierungsregeln gelten wie in der Argumentliste eines regulären Konstruktorinitialisierers.

Eigenschaften

Für jeden Datensatzparameter einer Datensatztypdeklaration gibt es ein entsprechendes öffentliches Eigenschaftselement, dessen Name und Typ aus der Wertparameterdeklaration entnommen werden.

Zur Information:

  • Eine öffentliche Auto-Eigenschaft für get und init wird definiert (siehe separate Accessor-Spezifikation init). Eine geerbte abstract-Eigenschaft mit passendem Typ wird überschrieben. Es ist ein Fehler, wenn die geerbte Eigenschaft nicht über public überschreibbaren get und init-Accessoren verfügt. Es handelt sich um einen Fehler, wenn die geerbte Eigenschaft verborgen ist.
    Die Auto-Eigenschaft wird mit dem Wert des entsprechenden primären Konstruktorparameters initialisiert. Attribute können auf die synthetisierte Auto-Eigenschaft und ihr zugehöriges Feld angewendet werden, indem Sie property:- oder field:-Ziele für Attribute verwenden, die syntaktisch auf den entsprechenden Datensatz-Parameter angewendet werden.

Dekonstruktion

Ein Datensatz mit mindestens einem Parameter synthetisiert eine öffentliche Instanz-Methode mit Rückgabewert namens „Deconstruct” mit einer Out-Parameter-Deklaration für jeden Parameter der primären Konstruktor-Deklaration. Jeder Parameter der Deconstruct-Methode weist den gleichen Typ wie der entsprechende Parameter der primären Konstruktordeklaration auf. Der Methodenkörper weist jedem Parameter der Deconstruct-Methode den Wert der Instanzeigenschaft desselben Namens zu. Die Methode kann explizit deklariert werden. Es ist ein Fehler, wenn die explizite Deklaration nicht mit der erwarteten Signatur oder Barrierefreiheit übereinstimmt oder statisch ist.

Das folgende Beispiel zeigt einen Positionsdatensatz R mit der vom Compiler erzeugten Deconstruct-Methode zusammen mit deren Verwendung:

public record R(int P1, string P2 = "xyz")
{
    public void Deconstruct(out int P1, out string P2)
    {
        P1 = this.P1;
        P2 = this.P2;
    }
}

class Program
{
    static void Main()
    {
        R r = new R(12);
        (int p1, string p2) = r;
        Console.WriteLine($"p1: {p1}, p2: {p2}");
    }
}

with Ausdruck

Ein with-Ausdruck ist eine neue Ausdrucksform mit der folgenden Syntax.

with_expression
    : switch_expression
    | switch_expression 'with' '{' member_initializer_list? '}'
    ;

member_initializer_list
    : member_initializer (',' member_initializer)*
    ;

member_initializer
    : identifier '=' expression
    ;

Ein with-Ausdruck ist nicht als Anweisung zulässig.

Ein with-Ausdruck ermöglicht eine „nicht destruktive Mutation”, die entwickelt wurde, um eine Kopie des Empfängerausdrucks mit Änderungen in Zuordnungen in der member_initializer_list zu erzeugen.

Ein gültiger with-Ausdruck weist einen Empfänger mit einem nicht ungültigen Typ auf. Der Empfängertyp muss ein Datensatz sein.

Auf der rechten Seite des with-Ausdrucks befindet sich ein member_initializer_list mit einer Sequenz von Zuweisungen an identifier, der ein zugriffsfreies Instanzfeld oder eine Eigenschaft vom Typ des Empfängers sein muss.

Zuerst wird die „Clone”-Methode des Empfängers (oben angegeben) aufgerufen, und das Ergebnis wird in den Typ des Empfängers konvertiert. Anschließend wird jeder member_initializer auf die gleiche Weise verarbeitet wie eine Zuweisung zu einem Feld oder einem Zugriff auf eine Eigenschaft des Konvertierungsergebnisses. Zuweisungen werden in lexikalischer Reihenfolge verarbeitet.