with 式 (C# リファレンス)with expression (C# reference)

C# 9.0 以降で使用可能な with 式は、指定されたプロパティと変更されたフィールドにより、レコード オペランドのコピーを生成します。Available in C# 9.0 and later, a with expression produces a copy of its record operand with the specified properties and fields modified:

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

前の例で示したように、変更するメンバーとその新しい値は、オブジェクト初期化子構文を使用して指定します。As the preceding example shows, you use object initializer syntax to specify what members to modify and their new values. with 式では、左側のオペランドがレコード型である必要があります。In a with expression, a left-hand operand must be of a record type.

次の例に示すように、with 式の結果は、式のオペランドと同じランタイム型になります。The result of a with expression has the same runtime type as the expression's operand, as the following example shows:

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 the case of a reference-type member, only the reference to an instance is copied when a record is copied. コピーと元のレコードの両方が、同じ参照型のインスタンスにアクセスできます。Both the copy and original record have access to the same reference-type instance. 次の例は、その動作を示します。The following example demonstrates that behavior:

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

すべてのレコード型には、"コピー コンストラクター" があります。Any record type has the copy constructor. これは、含んでいるレコード型の 1 つのパラメーターを持つコンストラクターです。That is a constructor with a single parameter of the containing record type. その引数の状態が新しいレコード インスタンスにコピーされます。It copies the state of its argument to a new record instance. with 式の評価では、コピー コンストラクターが呼び出され、元のレコードに基づいて新しいレコード インスタンスがインスタンス化されます。At evaluation of a with expression, the copy constructor gets called to instantiate a new record instance based on an original record. その後、新しいインスタンスは、指定された変更に従って更新されます。After that, the new instance gets updated according to the specified modifications. 既定では、コピー コンストラクターは暗黙的、つまり、コンパイラによって生成されます。By default, the copy constructor is implicit, that is, compiler-generated. レコード コピーのセマンティクスをカスタマイズする必要がある場合は、必要な動作が含まれるコピー コンストラクターを明示的に宣言します。If you need to customize the record copy semantics, explicitly declare a copy constructor with the desired behavior. 次の例では、明示的なコピー コンストラクターを使用して前の例が更新されます。The following example updates the preceding example with an explicit copy constructor. 新しいコピー動作では、レコードがコピーされるときにリスト参照ではなくリスト項目がコピーされます。The new copy behavior is to copy list items instead of a list reference when a record is copied:

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

C# 言語仕様C# language specification

詳細については、レコード機能提案メモの次のセクションを参照してください。For more information, see the following sections of the records feature proposal note:

関連項目See also