Condividi tramite


espressione with - La mutazione non distruttiva crea un nuovo oggetto con proprietà modificate

Un'espressione with produce una copia del relativo operando con le proprietà e i campi specificati modificati. Per specificare i membri da modificare e i relativi nuovi valori, usare la sintassi dell’inizializzatore dell'oggetto:

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'operando sinistro di un'espressione with può essere di tipo record. A partire da C# 10, un operando sinistro di un'espressione with può anche essere di un tipo di struttura o di un tipo anonimo.

Il risultato di un'espressione with ha lo stesso tipo di runtime dell'operando dell'espressione, come illustrato nell'esempio seguente:

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

Nel caso di un membro di tipo riferimento, viene copiato solo il riferimento a un'istanza del membro quando viene copiato un operando. Sia l'operando di copia che quello originale hanno accesso alla stessa istanza di tipo riferimento. L'esempio seguente illustra questo comportamento:

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

Semantica di copia personalizzata

Qualsiasi tipo di classe di record ha il costruttore di copia. Un costruttore di copia è un costruttore con un singolo parametro del tipo di record contenitore. Copia lo stato dell'argomento in una nuova istanza di record. Durante la valutazione di un'espressione with, il costruttore di copia viene chiamato per creare un'istanza di un nuovo record in base a un record originale. Successivamente, la nuova istanza viene aggiornata in base alle modifiche specificate. Per impostazione predefinita, il costruttore di copia è implicito, ovvero generato dal compilatore. Se è necessario personalizzare la semantica di copia del record, dichiarare in modo esplicito un costruttore di copia con il comportamento desiderato. Nell'esempio seguente viene aggiornato l'esempio precedente con un costruttore di copia esplicito. Il nuovo comportamento di copia consiste nel copiare gli elementi dell'elenco anziché un riferimento a un elenco quando viene copiato un record:

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

Non è possibile personalizzare la semantica di copia per i tipi di struttura.

Specifiche del linguaggio C#

Per altre informazioni, vedere le sezioni seguenti della nota relativa alla proposta di funzionalità dei record:

Vedi anche