日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > C# >内容正文

C#

C# 9.0中引入的新特性init和record的使用思考

發布時間:2023/12/4 C# 41 豆豆
生活随笔 收集整理的這篇文章主要介紹了 C# 9.0中引入的新特性init和record的使用思考 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

.NET 5.0已經發布,C# 9.0也為我們帶來了許多新特性,其中最讓我印象深刻的就是init和record type,很多文章已經把這兩個新特性討論的差不多了,本文不再詳細討論,而是通過使用角度來思考這兩個特性。

init

init是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種寫法到處去問。

record

record是一個非常有用的特性,它是不可變類型,其相等性是通過內部的幾個屬性來確定的,同時它支持我們以更加方便的方式、像定義值類型那樣來定義不可變引用類型。
我們把之前的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泛型接口。
編譯器為我們提供的幾個重要方法如下:

  • Equals

  • GetHashCode()

  • Clone

  • PrintMembers和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?IEnumerable<object>?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?IEnumerable<object>?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.FromJson<Address>();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-objects

  • https://deviq.com/value-object/

總結

以上是生活随笔為你收集整理的C# 9.0中引入的新特性init和record的使用思考的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。