Partager via


avec expression - Une mutation nonstructive crée un objet avec des propriétés modifiées

Une with expression produit une copie de son opérande avec les propriétés et champs spécifiés modifiés. Vous utilisez la syntaxe d’initialiseur d’objet pour spécifier les membres à modifier et leurs nouvelles valeurs :

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

L’opérande de gauche d’une with expression peut être d’un type d’enregistrement. Un opérande de gauche d’une with expression peut également être d’un type de structure ou d’un type anonyme.

Le résultat d’une with expression a le même type d’exécution que l’opérande de l’expression, comme l’illustre l’exemple suivant :

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

Dans le cas d’un membre de type référence, seule la référence à une instance de membre est copiée lorsqu’un opérande est copié. La copie et l’opérande d’origine ont accès à la même instance de type référence. L’exemple suivant illustre ce comportement :

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

Sémantique de copie personnalisée

Tout type de classe d’enregistrement a le constructeur de copie. Un constructeur de copie est un constructeur avec un seul paramètre du type d’enregistrement conteneur. Il copie l’état de son argument dans une nouvelle instance d’enregistrement. Lors de l’évaluation d’une with expression, le constructeur de copie est appelé pour instancier une nouvelle instance d’enregistrement basée sur un enregistrement d’origine. Après cela, la nouvelle instance est mise à jour en fonction des modifications spécifiées. Par défaut, le constructeur de copie est implicite, c’est-à-dire généré par le compilateur. Si vous devez personnaliser la sémantique de copie d’enregistrement, déclarez explicitement un constructeur de copie avec le comportement souhaité. L’exemple suivant met à jour l’exemple précédent avec un constructeur de copie explicite. Le nouveau comportement de copie consiste à copier des éléments de liste au lieu d’une référence de liste lorsqu’un enregistrement est copié :

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

Vous ne pouvez pas personnaliser la sémantique de copie pour les types de structure.

Spécification du langage C#

Pour plus d’informations, consultez les sections suivantes de la note de proposition de fonctionnalité d’enregistrements :

Voir aussi