C# 9.0 中的with表达式

Posted 全栈派

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C# 9.0 中的with表达式相关的知识,希望对你有一定的参考价值。

如果你还不太了解Init-only属性和record,请参考以下文章:




在处理不可变类型的时候,比较常见的操作是基于现有的不可变对象创建新的对象实例,以呈现新的对象实例状态。


本文中,我们先从一个类开始。


创建一个Person类,其属性都是init,如下:


public class Person{ public string FirstName { get; init; } public string LastName { get; init; }}


在上面类中,属性都是init的,只能在初始化的时候赋值,所以初始化之后不能改变,无法修改它们。这意味着,在使用该类时,如果要更改某些内容,则需要使用新的数据创建一个新的Person对象。所以,不可变类型或者init属性的对象表示的是特定时间点的数据状态,当需要更改时,你可以创建一个新对象,而不是随着需要而更改对象。


举个例子,初始化一个Person对象:

Person person = new Person{ FirstName = "San", LastName = "Zhang"};


假设现在需要把声明的Person对象的FirstName改成 "Fei",但是由于FirstName是仅用于初始化的属性,所以无法直接修改。只能通过创建一个新的Person对象,使用第一次初始化的PersonLastName为新对象的LastName赋值,然后再给新对象的FirstName一个值为"Fei"的初始值,代码片段如下:

Person person = new Person{ FirstName = "San",    LastName = "Zhang"};
Person newPerson = new Person{    FirstName = "Fei", LastName = person.LastName};


public class Person{ public string FirstName { get; init; } public string LastName { get; init; } public byte Gender { get; init; } public string Address { get; init; } public int Age { get; init; }}


按照刚才的场景,初始化然后再修改FirstName属性:

Person person = new Person{ FirstName = "San", LastName = "Zhang", Gender = 1, Address = "Beijing,China", Age = 22};Person newPerson = new Person{ LastName = person.LastName, FirstName = "Fei", Gender = person.Gender, Address = person.Address, Age = person.Age};Console.WriteLine(newPerson.FirstName);Console.WriteLine(newPerson.Address);


运行一下:

> dotnet runFeiBeijing,China


如果Person类里面有更多属性,则需要写更多类似的代码来满足我们的修改某些属性的值的需求。这种方式虽然能满足需求,但是处理起来效率非常低,需要手动从原来的对象的属性中给新对象中不需要修改的属性赋值,这样非常容易会漏掉某个字段的处理。


那么,是否有更好的处理办法呢?答案是:除了使用反射或序列化的方式之外,在C# 9.0中有更好办法。


我们先将Person改成record类型,如下:

public record Person{ public string FirstName { get; init; } public string LastName { get; init; } public byte Gender { get; init; } public string Address { get; init; } public int Age { get; init; }}


Person改成 record类型是因为在C# 9.0中,我们可以使用with表达式来更高效的满足类似的需求,with表达式可以更高效的从原有的对象上创建新的对象,将我们的代码简单的修改一下:

Person person = new Person{ FirstName = "San", LastName = "Zhang", Gender = 1, Address = "Beijing,China", Age = 22};Person newPerson = person with { FirstName = "Fei" };Console.WriteLine(newPerson.FirstName);Console.WriteLine(newPerson.Address);


运行一下,结果是相同的:

> dotnet runFeiBeijing,China


如果你熟悉对象的初始值设定项的语法,则很容易就能熟练使用with表达式。还可以在with表达式中一次设置多个属性的值:

Person newPerson = person with { FirstName = "Fei", Address = "Shanghai,China" };Console.WriteLine(newPerson.FirstName);Console.WriteLine(newPerson.Address);

运行结果如下:

> dotnet runFeiShanghai,China

处理不可变类型的数据时,创建原始对象的副本然后修改并使用副本,这种技巧称为“非破坏性修改”。


注:with表达式只能用于record类型,而不适用于普通的class类型。


最后,我们再做一个简单的校验:


在  中曾说到,record是一种特殊的引用类型,他具有值类型的行为,那么我们将修改后的副本再把相关的值还原回去,那么还原后的record对象实例是是否还与初始的时候一致呢?


让我们来验证一下:

Person person = new Person{ FirstName = "San", LastName = "Zhang", Gender = 1, Address = "Beijing,China", Age = 22};Person newPerson = person with { FirstName = "Fei", Address = "Shanghai,China" };Console.WriteLine(person == newPerson); //输出结果为: FalsePerson anotherPerson = newPerson with { FirstName = "San", Address = "Beijing,China" };Console.WriteLine(person == anotherPerson); //输出结果为: True


> dotnet runFalseTrue


由运行结果可以看出,在第一次使用with修改了属性值之后,newPersonperson是不相同的,因为属性值不一样;在第二次使用with修改属性之后,由于修改后anotherPerson的属性与第一个实例person的属性值一样,所以两个记录类型的Person实例是相等的。

以上是关于C# 9.0 中的with表达式的主要内容,如果未能解决你的问题,请参考以下文章

C# 9.0 中的新增功能

C# 9.0 新特性

C# 9.0 中的顶级语句

C# 9.0 中的新款匹配模式

C# 9.0 新特性预览

C# 9.0 中的新增功能