遂宁移动网站建设,江苏网站建设网络推广,京东商城网站建设教程,网站制作的报价大约是多少.NET 5.0已经发布#xff0c;C# 9.0也为我们带来了许多新特性#xff0c;其中最让我印象深刻的就是init和record type#xff0c;很多文章已经把这两个新特性讨论的差不多了#xff0c;本文不再详细讨论#xff0c;而是通过使用角度来思考这两个特性。initinit是C# 9.0中引… .NET 5.0已经发布C# 9.0也为我们带来了许多新特性其中最让我印象深刻的就是init和record type很多文章已经把这两个新特性讨论的差不多了本文不再详细讨论而是通过使用角度来思考这两个特性。initinit是C# 9.0中引入的新的访问器它允许被修饰的属性在对象初始化的时候被赋值其他场景作为只读属性的存在。直接使用的话可能感受不到init的意义所以我们先看看之前是如何设置属性为只读的。private set设置属性为只读设置只读属性有很多种方式本文基于private set来讨论。首先声明一个产品类如下代码所示我们把Id设置成了只读这个时候也就只能通过构造函数来赋值了。在通常情况下实体的唯一标识是不可更改的同时也要防止Id被意外更改。public class Product
{public Product(int id){this.Id id;}public int Id { get; private set; }//public int Id { get; }public string ProductName { get; set; }public string Description { get; set; }
}class Program
{static void Main(string[] args){Product product new Product(1){ProductName test001,Description Just a description};Console.WriteLine($Current Product Id: {product.Id},\n\rProduct Name: {product.ProductName}, \n\rProduct Description: {product.Description});//运行结果//Current Product Id: 1,//Product Name: test001,//Product Description: Just a descriptionConsole.ReadKey();}
}
record方式设置只读使用init方式是非常简单的只需要把private set改成init就行了public int Id { get; init; }
两者比较为了方便比较我们可以将ProductName设置成了private set然后通过ILSpy来查看一下编译后的代码看看编译后的Id和ProductName有何不同咋一看貌似没啥区别都使用到了initonly来修饰。但是如果仅仅只是替换声明方式那么这个新特性似乎就没有什么意义了。接下来我们看第二张图如图标记的那样区别还是很明显的通过init修饰的属性并没有完全替换掉set由此看来微软在设计init的时候还是挺用心思的也为后面的赋值留下了入口。instance void modreq([System.Runtime]System.Runtime.CompilerServices.IsExternalInit) set_Id (int32 value)
另外在赋值的时候使用private set修饰的属性需要定义构造函数通过构造函数赋值。而使用了init修饰的属性则不需要定义构造函数直接在对象初始化器中赋值即可。Product product new Product
{Id 1,ProductName test001,Description Just a description
};product.Id 2;//Error CS8852 Init-only property or indexer Product.Id can only be assigned in an object initializer, or on this or base in an instance constructor or an init accessor.
如上代码所示只读属性Id的赋值并没有在构造函数中赋值毕竟当一个类的只读字段十分多的时候构造函数也变得复杂。而且在赋值好之后无法修改这和我们对只读属性在通常情况下的理解是一致的。另外通过init修饰的好处便是省却了一部分只读属性在操作上的复杂性使得对象的声明与赋值更加直观。在合适的场景下选择最好的编程方式是程序员的一贯追求千万不要为了炫技而把init当成了茴字的第N种写法到处去问。recordrecord是一个非常有用的特性它是不可变类型其相等性是通过内部的几个属性来确定的同时它支持我们以更加方便的方式、像定义值类型那样来定义不可变引用类型。我们把之前的Product类改成record类型如下所示public record Product
{public Product(int id, string productName, string description) (Id, ProductName, Description) (id, productName, description);public int Id { get; }public string ProductName { get; }public string Description { get; }
}
然后查看一下IL可以看到record会被编译成类同时继承了System.Object并实现了IEquatable泛型接口。编译器为我们提供的几个重要方法如下EqualsGetHashCode()ClonePrintMembers和ToString()比较重要的三个方法Equals通过图片中的代码我们知道比较两个record对象首先需要比较类型是否相同然后再依次比较内部属性。GetHashCode()record类型通过基类型以及所有的属性及字段的方式来计算HashCode这在整个继承层次结构中增强了基于值的相等性也就意味着两个同名同姓的人不会被认为是同一个人Clone这个方法貌似非常简单实在看不出有什么特别的地方那么我们通过后面的内容再来解释这个方法。record在DDD值对象中的应用record之前的定义方式了解DDD值对象的小伙伴应该想到了record类型的特性非常像DDD中关于值对象的描述比如不可变性、其相等于是基于其内部的属性的等等我们先来看下值类型的定义方式。public abstract class ValueObject
{public static bool operator (ValueObject left, ValueObject right){if (ReferenceEquals(left, null) ^ ReferenceEquals(right, null)){return false;}return ReferenceEquals(left, null) || left.Equals(right);}public static bool operator !(ValueObject left, ValueObject right){return !(left right);}protected abstract IEnumerableobject GetEqualityComponents();public override bool Equals(object obj){if (obj null || obj.GetType() ! GetType()){return false;}var other (ValueObject)obj;return this.GetEqualityComponents().SequenceEqual(other.GetEqualityComponents());}public override int GetHashCode(){return GetEqualityComponents().Select(x x ! null ? x.GetHashCode() : 0).Aggregate((x, y) x ^ y);}// Other utility methods
}
public class Address : ValueObject
{public string Street { get; private set; }public string City { get; private set; }public string State { get; private set; }public string Country { get; private set; }public string ZipCode { get; private set; }public Address(string street, string city, string state, string country, string zipcode){Street street;City city;State state;Country country;ZipCode zipcode;}protected override IEnumerableobject GetEqualityComponents(){// Using a yield return statement to return each element one at a timeyield return Street;yield return City;yield return State;yield return Country;yield return ZipCode;}public override string ToString(){return $Street: {Street}, City: {City}, State: {State}, Country: {Country}, ZipCode: {ZipCode};}
}
main方法如下static void Main(string[] args)
{Address address1 new Address(aaa, bbb, ccc, ddd, fff);Console.WriteLine($address1: {address1});Address address2 new Address(aaa, bbb, ccc, ddd, fff);Console.WriteLine($address2: {address2});Console.WriteLine($address1 address2: {address1 address2});string jsonAddress1 address1.ToJson();Address jsonAddress1Deserialize jsonAddress1.FromJsonAddress();Console.WriteLine($jsonAddress1Deserialize address1: {jsonAddress1Deserialize address1});Console.ReadKey();
}
运行结果如下基于class:
address1: Street: aaa, City: bbb, State: ccc, Country: ddd, ZipCode: fff
address2: Street: aaa, City: bbb, State: ccc, Country: ddd, ZipCode: fff
address1 address2: True
jsonAddress1Deserialize address1: True
采用record方式定义如果有大量的值对象需要我们编写这无疑是加重我们的开发量的这个时候record就派上用场了最简洁的record风格的代码如下所示只有一行public record Address(string Street, string City, string State, string Country, string ZipCode);
IL代码如下图所示从图中我们也可以看到record类型的对象默认情况下用到了init来限制属性的只读特性。main方法代码不变运行结果也没有因为Address从class变成record而发生改变基于record:
address1: Street: aaa, City: bbb, State: ccc, Country: ddd, ZipCode: fff
address2: Street: aaa, City: bbb, State: ccc, Country: ddd, ZipCode: fff
address1 address2: True
jsonAddress1Deserialize address1: True
如此看来我们的代码节省的不止一点点而是太多太多了是不是很爽啊。record对象属性值的更改使用方式如下class Program
{static void Main(string[] args){Address address1 new Address(aaa, bbb, ccc, ddd, fff);Console.WriteLine($1. address1: {address1});Address addressWith address1 with { Street ############ };Console.ReadKey();}
}public record Address(string Street, string City, string State, string Country, string ZipCode);
通过ILSpy查看如下所示private static void Main(string[] args)
{Address address1 new Address(aaa, bbb, ccc, ddd, fff);Console.WriteLine($1. address1: {address1});Address address2 address1.Clone$();address2.Street ############;Address addressWith address2;Console.ReadKey();
}
由此可以看到record在更改的时候实际上是通过调用Clone而产生了浅拷贝的对象这也非常符合DDD ValueObject的设计理念。参考https://docs.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/implement-value-objectshttps://deviq.com/value-object/