Ausdruck „with“: nichtdestruktive Mutation erstellt ein neues Objekt mit veränderten Eigenschaften

Ein with-Ausdruck erstellt eine Kopie seines Operanden mit angegebenen Eigenschaften und geänderten Feldern. Sie verwenden die Objektinitialisierersyntax, um anzugeben, welche Member bearbeitet und welche neuen Werte dazu verwendet werden sollen:

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. Ab C# 10 kann der linke Operand eines with-Ausdrucks 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 der Member einen Verweistyp aufweist, wird beim Kopieren eines Operanden nur der Verweis auf eine Memberinstanz kopiert. Sowohl der kopierte als auch der ursprüngliche Operand können auf dieselbe Instanz vom Typ Verweis zugreifen. Das folgende Beispiel veranschaulicht dieses Verhalten:

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 der angegebenen Änderungen aktualisiert. Standardmäßig ist der Kopierkonstruktor implizit, das heißt, vom Compiler generiert. Wenn Sie die Kopiersemantik des Datensatzes anpassen möchten, deklarieren Sie explizit einen Kopierkonstruktor mit gewünschtem Verhalten. Das folgende Beispiel aktualisiert das vorherige Beispiel mit einem expliziten Kopierkonstruktor. Das neue Kopierverhalten besteht darin, Listenelemente anstatt 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.

C#-Sprachspezifikation

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

Weitere Informationen