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对象,使用第一次初始化的Person的LastName为新对象的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 run
Fei
Beijing,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 run
Fei
Beijing,China
如果你熟悉对象的初始值设定项的语法,则很容易就能熟练使用with表达式。还可以在with表达式中一次设置多个属性的值:
Person newPerson = person with { FirstName = "Fei", Address = "Shanghai,China" };
Console.WriteLine(newPerson.FirstName);
Console.WriteLine(newPerson.Address);
运行结果如下:
> dotnet run
Fei
Shanghai,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); //输出结果为: False
Person anotherPerson = newPerson with { FirstName = "San", Address = "Beijing,China" };
Console.WriteLine(person == anotherPerson); //输出结果为: True
> dotnet run
False
True
由运行结果可以看出,在第一次使用with修改了属性值之后,newPerson和person是不相同的,因为属性值不一样;在第二次使用with修改属性之后,由于修改后anotherPerson的属性与第一个实例person的属性值一样,所以两个记录类型的Person实例是相等的。
以上是关于C# 9.0 中的with表达式的主要内容,如果未能解决你的问题,请参考以下文章