Freigeben über


Der with Ausdruck - Nicht destruktive Mutation erstellt ein neues Objekt mit geänderten Eigenschaften.

Ein with-Ausdruck erstellt eine Kopie seines Operanden mit angegebenen Eigenschaften und geänderten Feldern. Sie verwenden die Objektinitialisierungssyntax, um anzugeben, welche Elemente geändert werden sollen, und deren neue Werte:

using System;

public class WithExpressionBasicExample
{
    public record NamedPoint(string Name, int X, int Y);

    public static void Main()
    {
        var p1 = new NamedPoint("A", 0, 0);
        Console.WriteLine($"{nameof(p1)}: {p1}");  // output: p1: NamedPoint { Name = A, X = 0, Y = 0 }
        
        var p2 = p1 with { Name = "B", X = 5 };
        Console.WriteLine($"{nameof(p2)}: {p2}");  // output: p2: NamedPoint { Name = B, X = 5, Y = 0 }
        
        var p3 = p1 with 
            { 
                Name = "C", 
                Y = 4 
            };
        Console.WriteLine($"{nameof(p3)}: {p3}");  // output: p3: NamedPoint { Name = C, X = 0, Y = 4 }

        Console.WriteLine($"{nameof(p1)}: {p1}");  // output: p1: NamedPoint { Name = A, X = 0, Y = 0 }

        var apples = new { Item = "Apples", Price = 1.19m };
        Console.WriteLine($"Original: {apples}");  // output: Original: { Item = Apples, Price = 1.19 }
        var saleApples = apples with { Price = 0.79m };
        Console.WriteLine($"Sale: {saleApples}");  // output: Sale: { Item = Apples, Price = 0.79 }
    }
}

Der linke Operand eines with-Ausdrucks kann einen Datensatztyp aufweisen. Ein linker Operand eines with-Ausdrucks kann auch einen Strukturtyp oder einen anonymen Typ aufweisen.

Das Ergebnis eines with-Ausdrucks weist denselben Laufzeittyp auf wie der Operand des Ausdrucks. Sehen Sie sich dazu das folgende Beispiel an:

using System;

public class InheritanceExample
{
    public record Point(int X, int Y);
    public record NamedPoint(string Name, int X, int Y) : Point(X, Y);

    public static void Main()
    {
        Point p1 = new NamedPoint("A", 0, 0);
        Point p2 = p1 with { X = 5, Y = 3 };
        Console.WriteLine(p2 is NamedPoint);  // output: True
        Console.WriteLine(p2);  // output: NamedPoint { X = 5, Y = 3, Name = A }
    }
}

Wenn ein Element ein Verweistyp ist, wird nur der Verweis auf eine Elementinstanz kopiert, wenn ein Operand kopiert wird. Sowohl der kopierte als auch der ursprüngliche Operand haben Zugriff auf dieselbe Referenztyp-Instanz. Im folgenden Beispiel wird dieses Verhalten veranschaulicht:

using System;
using System.Collections.Generic;

public class ExampleWithReferenceType
{
    public record TaggedNumber(int Number, List<string> Tags)
    {
        public string PrintTags() => string.Join(", ", Tags);
    }

    public static void Main()
    {
        var original = new TaggedNumber(1, new List<string> { "A", "B" });

        var copy = original with { Number = 2 };
        Console.WriteLine($"Tags of {nameof(copy)}: {copy.PrintTags()}");
        // output: Tags of copy: A, B

        original.Tags.Add("C");
        Console.WriteLine($"Tags of {nameof(copy)}: {copy.PrintTags()}");
        // output: Tags of copy: A, B, C
    }
}

Benutzerdefinierte Kopiersemantik

Jeder Datensatzklassentyp weist den Kopierkonstruktor auf. Bei einem Kopierkonstruktor handelt es sich um einen Konstruktor mit einzelnem Parameter des enthaltenden Datensatztyps. Der Zustand des dazugehörigen Arguments wird in eine neue Datensatzinstanz kopiert. Wenn ein with-Ausdruck ausgewertet wird, wird der Kopierkonstruktor aufgerufen, um eine neue Datensatzinstanz basierend auf dem ursprünglichen Datensatz zu instanziieren. Danach wird die neue Instanz entsprechend den angegebenen Änderungen aktualisiert. Standardmäßig ist der Kopierkonstruktor implizit, d. h. compilergeneriert. Wenn Sie die Datensatzkopiesemantik anpassen müssen, deklarieren Sie explizit einen Kopierkonstruktor mit dem gewünschten Verhalten. Das folgende Beispiel aktualisiert das vorherige Beispiel mit einem expliziten Kopierkonstruktor. Das neue Kopierverhalten besteht darin, Listenelemente anstelle eines Listenverweises zu kopieren, wenn ein Datensatz kopiert wird:

using System;
using System.Collections.Generic;

public class UserDefinedCopyConstructorExample
{
    public record TaggedNumber(int Number, List<string> Tags)
    {
        protected TaggedNumber(TaggedNumber original)
        {
            Number = original.Number;
            Tags = new List<string>(original.Tags);
        }

        public string PrintTags() => string.Join(", ", Tags);
    }

    public static void Main()
    {
        var original = new TaggedNumber(1, new List<string> { "A", "B" });

        var copy = original with { Number = 2 };
        Console.WriteLine($"Tags of {nameof(copy)}: {copy.PrintTags()}");
        // output: Tags of copy: A, B

        original.Tags.Add("C");
        Console.WriteLine($"Tags of {nameof(copy)}: {copy.PrintTags()}");
        // output: Tags of copy: A, B
    }
}

Die Kopiersemantik für Strukturtypen kann nicht angepasst werden.

Von Bedeutung

In den vorherigen Beispielen sind alle Eigenschaften unabhängig. Keine der Eigenschaften wird aus anderen Eigenschaftswerten berechnet. Ein with Ausdruck kopiert zuerst die vorhandene Datensatzinstanz und ändert dann alle im with Ausdruck angegebenen Eigenschaften oder Felder. Berechnete Eigenschaften in record Typen sollten beim Zugriff berechnet und nicht initialisiert werden, wenn die Instanz erstellt wird. Andernfalls könnte eine Eigenschaft den berechneten Wert basierend auf der ursprünglichen Instanz und nicht der geänderten Kopie zurückgeben. Weitere Informationen finden Sie im Sprachreferenzartikel zu record Typen.

C#-Sprachspezifikation

Weitere Informationen finden Sie in den folgenden Abschnitten des Artikels Datensätze:

Weitere Informationen