with expression - 非破壊的変異により、変更されたプロパティを持つ新しいオブジェクトが作成されます
with
式は、指定したプロパティとフィールドが変更されたオペランドのコピーを生成します。 オブジェクト初期化子 構文を使用して、変更するメンバーとその新しい値を指定します。
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 }
}
}
with
式の左側のオペランドをレコード型にすることができます。 with
式の左側のオペランドとして構造体型または 匿名型を使用することもできます。
with
式の結果は、次の例に示すように、式のオペランドと同じランタイム型になります。
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 }
}
}
参照型メンバーの場合、オペランドのコピー時にコピーされるのはメンバー インスタンスへの参照だけです。 コピーと元のオペランドの両方が、同じ参照型インスタンスにアクセスできます。 次の例は、その動作を示しています。
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
}
}
カスタム コピー セマンティクス
レコードクラス型にはコピーコンストラクターがあります。 コピー コンストラクター は、含まれるレコード型の 1 つのパラメーターを持つコンストラクターです。 引数の状態を新しいレコード インスタンスにコピーします。 with
式の評価時に、コピー コンストラクターが呼び出され、元のレコードに基づいて新しいレコード インスタンスがインスタンス化されます。 その後、指定した変更に従って新しいインスタンスが更新されます。 既定では、コピー コンストラクターは暗黙的です。つまり、コンパイラによって生成されます。 レコード コピー セマンティクスをカスタマイズする必要がある場合は、目的の動作でコピー コンストラクターを明示的に宣言します。 次の例では、前の例を明示的なコピー コンストラクターで更新します。 新しいコピー動作では、レコードのコピー時にリスト参照ではなくリスト アイテムをコピーします。
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# 言語仕様
詳細については、レコード機能提案ノートの次のセクションを参照してください。
関連項目
.NET