met expressie - Niet-destructieve mutatie creëert een nieuw object met gewijzigde eigenschappen

Een with expressie produceert een kopie van de operand met de opgegeven eigenschappen en velden die zijn gewijzigd. U gebruikt de syntaxis van de object-initialisatiefunctie om op te geven welke leden moeten worden gewijzigd en de nieuwe waarden:

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

De linkeroperand van een with expressie kan van een recordtype zijn. Vanaf C# 10 kan een linkeroperand van een with expressie ook van een structuurtype of een anoniem type zijn.

Het resultaat van een with expressie heeft hetzelfde runtimetype als de operand van de expressie, zoals in het volgende voorbeeld wordt weergegeven:

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

In het geval van een verwijzingstypelid wordt alleen de verwijzing naar een lidexemplaren gekopieerd wanneer een operand wordt gekopieerd. Zowel de kopie als de oorspronkelijke operand hebben toegang tot hetzelfde exemplaar van het verwijzingstype. In het volgende voorbeeld ziet u dat gedrag:

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

Aangepaste semantiek voor kopiëren

Elk recordklassetype heeft de kopieerconstructor. Een kopieerconstructor is een constructor met één parameter van het recordtype. De status van het argument wordt gekopieerd naar een nieuw recordexemplaren. Bij de evaluatie van een with expressie wordt de kopieerconstructor aangeroepen om een nieuw recordexemplaar te instantiëren op basis van een oorspronkelijke record. Daarna wordt het nieuwe exemplaar bijgewerkt op basis van de opgegeven wijzigingen. Standaard is de kopieerconstructor impliciet, dat wil gezegd, gegenereerd door compiler. Als u de semantiek van het kopiëren van records wilt aanpassen, declareert u expliciet een kopieerconstructor met het gewenste gedrag. In het volgende voorbeeld wordt het voorgaande voorbeeld bijgewerkt met een expliciete kopieerconstructor. Het nieuwe kopieergedrag is het kopiëren van lijstitems in plaats van een lijstreferentie wanneer een record wordt gekopieerd:

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

U kunt de kopieersemantiek voor structuurtypen niet aanpassen.

C#-taalspecificatie

Zie de volgende secties van de notitie over het voorstel voor records:

Zie ook