我遇到过这段代码:
var rectangle = new Rectangle(420, 69);
var newOne = rectangle with { Width = 420 }
我想知道 C# 代码中的
with
关键字。它是做什么用的?以及如何使用它?它给语言带来了什么好处?
它是表达式中使用的运算符,可以更轻松地复制对象,覆盖它的一些公共属性/字段(可选) 带有表达式 - MSDN
目前只能与记录一起使用。但也许将来就没有这样的限制了(假设)。
这是一个如何使用它的示例:
// Declaring a record with a public property and a private field
record WithOperatorTest
{
private int _myPrivateField;
public int MyProperty { get; set; }
public void SetMyPrivateField(int a = 5)
{
_myPrivateField = a;
}
}
现在让我们看看如何使用
with
运算符:
var firstInstance = new WithOperatorTest
{
MyProperty = 10
};
firstInstance.SetMyPrivateField(11);
var copiedInstance = firstInstance with { };
// now "copiedInstance" also has "MyProperty" set to 10 and "_myPrivateField" set to 11.
var thirdCopiedInstance = copiedInstance with { MyProperty = 100 };
// now "thirdCopiedInstance " also has "MyProperty" set to 100 and "_myPrivateField" set to 11.
thirdCopiedInstance.SetMyPrivateField(-1);
// now "thirdCopiedInstance " also has "MyProperty" set to 100 and "_myPrivateField" set to -1.
MSDN 中的参考类型注意事项:
对于引用类型成员,复制操作数时仅复制对成员实例的引用。副本和原始操作数都可以访问相同的引用类型实例。
可以通过修改记录类型的复制构造函数来修改该逻辑。引用自MSDN:
默认情况下,复制构造函数是隐式的,即编译器生成的。如果您需要自定义记录复制语义,请显式声明具有所需行为的复制构造函数。
protected WithOperatorTest(WithOperatorTest original)
{
// Logic to copy reference types with new reference
}
就它带来的好处而言,我认为现在应该非常明显了,它使实例的复制变得更加容易和方便。
本质上,当您使用
with
运算符时,它会创建一个新的对象实例,当前仅用于记录。这个新的对象实例是通过从源对象复制值并覆盖目标对象中的特定命名属性来创建的。
例如,不要这样做:
var person = new Person("John", "Doe")
{
MiddleName = "Patrick"
};
var modifiedPerson = new Person(person.FirstName, person.LastName)
{
MiddleName = "William"
};
你可以这样做:
var modifiedPerson = person with
{
MiddleName = "Patrick"
};
基本上,你会编写更少的代码。
简短的回答如下: 添加了 C# 中的
with
关键字,以便更轻松地复制复杂对象,并可以覆盖某些公共属性。
已接受的答案中已简要提供了示例。
A with 表达式 会生成其操作数的副本,并修改指定的属性和字段。
从 C# 10 开始,with 表达式的 LHS 操作数现在可以是 - records、structs 或 anonymous 类型。
记录:
public record Person(string FirstName, string LastName);
public static class Program
{
public static void Main()
{
Person person = new("Nancy", "Davolio");
Console.WriteLine(person);
// output: Person { FirstName = Nancy, LastName = Davolio }
var personCreatedUsingWithExpression = person with { FirstName = "Dave" };
Console.WriteLine(personCreatedUsingWithExpression);
// output: Person { FirstName = Dave, LastName = Davolio }
}
}
包含引用类型成员的记录:复制操作数时仅复制对成员实例的引用。副本和原始操作数都可以访问相同的引用类型实例。
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 } }
结构:由于 C# 中的
record
从技术上讲是 struct
,因此更容易记住 with
表达式也适用于 struct
。
public readonly struct Coords
{
public Coords(double x, double y)
{
X = x;
Y = y;
}
public double X { get; init; }
public double Y { get; init; }
public override string ToString() => $"({X}, {Y})";
}
public static void Main()
{
var p1 = new Coords(0, 0);
Console.WriteLine(p1); // output: (0, 0)
var p2 = p1 with { X = 3 };
Console.WriteLine(p2); // output: (3, 0)
var p3 = p1 with { X = 1, Y = 4 };
Console.WriteLine(p3); // output: (1, 4)
}
匿名类型:
var apple = new { Item = "apples", Price = 1.35 };
var onSale = apple with { Price = 0.79 };