with 表达式 - 非破坏性突变创建具有修改属性的新对象
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
表达式的左侧操作数可以是一个记录类型。 从 C# 10 开始,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
}
}
自定义复制语义
任何记录类类型都具有复制构造函数。 复制构造函数是一个包含记录类型的单个参数的构造函数。 它将参数的状态复制到新的记录实例。 在评估 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# 语言规范
有关详细信息,请参阅记录功能建议说明的以下部分: