with expression – Nedestruktivní mutační vytvoření nového objektu s upravenými vlastnostmi

Výraz, který with je k dispozici v jazyce C# 9.0 a novějším, vytvoří kopii svého operandu se změněnými zadanými vlastnostmi a poli. pomocí syntaxe inicializátoru objektů určíte, které členy se mají upravit, a jejich nové hodnoty:

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 }
    }
}

V jazyce C# 9.0 musí být levý operand with výrazu záznamového typu. Počínaje jazykem C# 10 může být levý operand with výrazu také typu struktury nebo anonymního typu.

Výsledek výrazu with má stejný typ za běhu jako operand výrazu, jak ukazuje následující příklad:

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 }
    }
}

V případě člena typu odkazu se při zkopírování operandu zkopíruje pouze odkaz na instanci člena. Kopie i původní operand mají přístup ke stejné instanci typu odkazu. Následující příklad ukazuje toto chování:

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
    }
}

Vlastní sémantika kopírování

Libovolný typ třídy záznamu má konstruktor kopírování. Konstruktor kopírování je konstruktor s jedním parametrem obsahujícího typu záznamu. Zkopíruje stav argumentu do nové instance záznamu. Při vyhodnocení výrazu with se zavolá konstruktor kopírování, který vytvoří instanci nového záznamu na základě původního záznamu. Poté se nová instance aktualizuje podle zadaných úprav. Ve výchozím nastavení je konstruktor kopírování implicitní, tj. vygenerovaný kompilátorem. Pokud potřebujete přizpůsobit sémantiku kopírování záznamu, explicitně deklarujte konstruktor kopírování s požadovaným chováním. Následující příklad aktualizuje předchozí příklad explicitním konstruktorem kopírování. Novým chováním kopírování je kopírování položek seznamu místo odkazu na seznam při kopírování záznamu:

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
    }
}

Sémantiku kopírování pro typy struktur není možné přizpůsobit.

specifikace jazyka C#

Další informace najdete v následujících částech poznámky k návrhu funkce záznamů:

Viz také