微軟正在推動C#9.0的開發,C#9.0將成為.NET5開發平台的一部分,估計於11月發布。微軟.NET團隊C#首席設計師MadsTorgersen表明,C#9.0已初具規模,本文就共享下該言語下一版別中添
加的一些主要功能。
C#的每個新版別都力求提升通用編程方面的清晰度與簡略性,C#9.0也不破例,尤其重視支撐數據形狀的簡潔與不可變表明。下面,咱們就來詳細介紹!
僅可初始化的特點
方針的初始化器十分了不得。它們為客戶端創立方針供給了一種十分靈活且易於閱覽的格局,並且特別合適嵌套方針的創立,咱們能夠經過嵌套方針一次性創立整個方針樹。下面是一個簡略的比方:
newPerson{FirstName="Scott",LastName="Hunter"}
方針初始化器還能夠讓程序員免於編寫很多類型的結構樣板代碼,他們只需編寫一些特點即可!
publicclassPerson{publicstringFirstName{get;set;}publicstringLastName{get;set;}}
目前的一大限制是,特點有必要是可變的,只要這樣方針初始化器才干起作用,由於它們需求首要調用方針的結構函數(在這種狀況下調用的是默許的無參結構函數),然后分配給特點設置器。
僅可初始化的特點能夠解決這個問題!它們引入了init拜訪器。init拜訪器是set拜訪器的變體,它只能在方針初始化期間調用:
publicclassPerson{publicstringFirstName{get;init;}publicstringLastName{get;init;}}
在這種聲明下,上述客戶端代碼依然合法,可是后續假如你想為FirstName和LastName特點賦值就會犯錯。
初始化拜訪器和只讀字段
由於init拜訪器只能在初始化期間被調用,所以它們能夠修正地點類的只讀字段,就像結構函數相同。
publicclassPerson{privatereadonlystringfirstName;privatereadonlystringlastName;
publicstringFirstName{get=>firstName;init=>firstName=(value??thrownewArgumentNullException(nameof(FirstName)));}publicstringLastName{get=>lastName;init=>
lastName=(value??thrownewArgumentNullException(nameof(LastName)));}}
記載
假如你想保持某個特點不變,那么僅可初始化的特點十分有用。假如你期望整個方針都不可變,並且期望其行為宛如一個值,那么就應該考慮將其聲明為記載:
publicdataclassPerson{publicstringFirstName{get;init;}publicstringLastName{get;init;}}
上述類聲明中的data關鍵字表明這是一個記載,因而它具備了其他一些相似於值的行為,后面咱們將深入評論。一般而言,咱們更應該將記載視為“值”(數據),而非方針。它們不具備可變的封裝
狀況。相反,你能夠經過創立表明新狀況的新記載來表明隨着時刻發作的變化。記載不是由標識確認,而是由其內容確認。
With表達式
處理不可變數據時,一種常見的方式是運用現有的值創立新值以表明新狀況。例如,假如想修正或人的姓氏,那么咱們會用一個新方針來表明,這個方針除了姓氏之外和舊方針完全相同。通常咱們稱
該技能為非破壞性修正。記載代表的不是某段時刻的某個人,而是給定時刻點上這個人的狀況。
為了幫助大家習慣這種編程風格,記載答應運用一種新的表達辦法:with表達式:
varotherPerson=personwith{LastName="Hanselman"};
with表達式運用方針初始化的語法來說明新方針與舊方針之間的區別。你能夠指定多個特點。
記載隱式地界說了一個protected“仿制結構函數”,這種結構函數運用現有的記載方針,將字段逐個仿制到新的記載方針中:
protectedPerson(Personoriginal){/*copyallthefields*/}//generated
with表達式會調用仿制結構函數,然后在其上應用方針初始化器,以相應地更改特點。
假如你不喜歡主動生成的仿制結構函數,那么也能夠自己界說,with表達式就會調用自界說的仿制結構函數。
依據值的持平性
一切方針都會從object類承繼一個虛的Equals(object)辦法。在調用靜態辦法Object.Equals(object,object)且兩個參數均不為null時,該Equals(object)就會被調用。
結構體能夠重載這個辦法,取得“依據值的持平性”,即遞歸調用Equals來比較結構的每個字段。記載也相同。
這意味着,假如兩個記載方針的值共同,則二者持平,但兩者不一定是同一方針。例如,假如咱們再次修正前面那個人的姓氏:
varoriginalPerson=otherPersonwith{LastName="Hunter"};
現在,ReferenceEquals(person,originalPerson)=false(它們不是同一個方針),但Equals(person,originalPerson)=true(它們擁有相同的值)。
假如你不喜歡主動生成的Equals覆蓋默許的逐字段比較的行為,則能夠編寫自己的Equals重載。你只需求保證你理解依據值的持平性在記載中的作業原理,尤其是在涉及承繼的狀況下,詳細的內容我
們稍后再做介紹。
除了依據值的Equals之外,還有一個依據值的GetHashCode()重載辦法。
數據成員
在絕大多數狀況下,記載都是不可變的,它們的僅可初始化的特點是揭露的,能夠經過with表達式進行非破壞性修正。為了優化這種最常見的狀況,咱們改變了記載中相似於stringFirstName這種成
員聲明的默許意義。在其他類和結構聲明中,這種聲明表明私有字段,但在記載中,這相當於揭露的、僅可初始化的主動特點!因而,如下聲明:
publicdataclassPerson{stringFirstName;stringLastName;}
與之前提到過的下述聲明完全相同:
publicdataclassPerson{publicstringFirstName{get;init;}publicstringLastName{get;init;}}
咱們以為這種辦法能夠讓記載更加美麗而清晰。假如你需求私有字段,則能夠清晰增加private修飾符:
privatestringfirstName;
方位記載
有時,用參數方位來聲明記載會很有用,內容能夠依據結構函數參數的方位來指定,並且能夠經過方位解構來提取。
你完全能夠在記載中指定自己的結構函數和析構函數:
publicdataclassPerson{stringFirstName;stringLastName;publicPerson(stringfirstName,stringlastName)=>(FirstName,LastName)=(firstName,lastName);publicvoid
Deconstruct(outstringfirstName,outstringlastName)=>(firstName,lastName)=(FirstName,LastName);}
可是,咱們能夠用更短的語法表達完全相同的內容(運用成員變量的大小寫辦法來命名參數):
publicdataclassPerson(stringFirstName,stringLastName);
上述聲明晰僅可初始化的揭露的主動特點以及結構函數和析構函數,因而你能夠這樣寫:
varperson=newPerson("Scott","Hunter");//positionalconstructionvar(f,l)=person;//positionaldeconstruction
假如你不喜歡生成的主動特點,則能夠界說自己的同名特點,這樣生成的結構函數和析構函數就會主動運用自己界說的特點。
記載和修正
記載的語義是依據值的,因而在可變的狀況中無法很好地運用。幻想一下,假如咱們將記載方針放入字典,那么就只能經過Equals和GethashCode找到了。可是,假如記載更改了狀況,那么在判別相
等時它代表的值也會發作改變!或許咱們就找不到它了!在哈希表的完成中,這個性質乃至或許破壞數據結構,由於數據的寄存方位是依據它“抵達”哈希表時的哈希值決議的!
並且,記載也或許有一些運用內部可變狀況的高檔辦法,這些辦法完全是合理的,例如緩存。可是能夠考慮經過手工重載默許的行為來忽略這些狀況。
with表達式與承繼
眾所周知,考慮承繼時依據值的持平性和非破壞性修正是一個難題。下面咱們在示例中增加一個承繼的記載類Student:
publicdataclassPerson{stringFirstName;stringLastName;}publicdataclassStudent:Person{intID;}
在如下with表達式的示例中,咱們實踐創立一個Student,然后將其存儲到Person變量中:
Personperson=newStudent{FirstName="Scott",LastName="Hunter",ID=GetNewId()};otherPerson=personwith{LastName="Hanselman"};
在最后一行的with表達式中,編譯器並不知道person實踐上包括一個Student。並且,即便otherPerson不是Student方針,它也不是合法的副本,由於它包括了與第一個方針相同的ID特點。
C#解決了這個問題。記載有一個躲藏的虛辦法,能夠保證“克隆”整個方針。每個承繼的記載類型都會經過重載這個辦法來調用該類型的仿制結構函數,而承繼記載的仿制結構函數會調用基類的仿制
結構函數。with表達式只需調用這個躲藏“clone”辦法,然后在結果上應用方針初始化器即可。
依據值的持平性與承繼
與with表達式的支撐相似,依據值的持平性也有必要是“虛的”,即兩個Student方針比較時需求比較一切字段,即便在比較時,能夠靜態地得知類型是基類,比方Person。這一點經過重寫已經是虛方
法的Equals辦法能夠輕松完成。
然而,持平性還有另外一個難題:假如需求比較兩個不同類型的Person怎么辦?咱們不能簡略地選擇其中一個來決議是否持平:持平性應該是對稱的,因而不管兩個方針中的哪個首要呈現,結果都應
該相同。換句話說,二者之間有必要就持平性達成共同!
咱們來舉例說明這個問題:
Personperson1=newPerson{FirstName="Scott",LastName="Hunter"};Personperson2=newStudent{FirstName="Scott",LastName="Hunter",ID=GetNewId()};
這兩個方針互相持平嗎?person1或許會以為持平,由於person2擁有Person的一切字段,但person2或許會有不同的觀點!咱們需求保證二者都認同它們是不同的方針。
C#能夠主動為你解決這個問題。詳細的完成辦法是:記載擁有一個名為EqualityContract的受維護虛特點。每個承繼的記載都會重載這個特點,並且為了比較持平,兩個方針有必要具有相同的
EqualityContract。
頂層程序
運用C#編寫一個簡略的程序需求很多的樣板代碼:
usingSystem;classProgram{staticvoidMain(){Console.WriteLine("HelloWorld!");}}
這不僅對初學者來說難度太高,並且代碼紊亂,縮進等級也太多。
在C#9.0中,你只需編寫頂層的主程序:
usingSystem;
Console.WriteLine("HelloWorld!");
任何句子都能夠。程序有必要位於using之后,文件中的任何類型或稱號空間聲明之前,並且只能在一個文件中,就像只要一個Main辦法相同。
假如你想回來狀況代碼,則能夠運用這種寫法。假如你想await,那么也能夠這么寫。此外,假如你想拜訪命令行參數,則args可作為“魔術”參數運用。
局部函數是句子的一種方式,並且也能夠在頂層程序中運用。在頂層句子之外的任何地方調用局部函數都會報錯。
改善后的方式匹配
C#9.0中增加了幾種新的方式。下面咱們經過如下方式匹配教程的代碼片段來看看這些新方式:
publicstaticdecimalCalculateToll(objectvehicle)=>vehicleswitch{...
DeliveryTrucktwhent.GrossWeightClass>5000=>10.00m+5.00m,DeliveryTrucktwhent.GrossWeightClass<3000=>10.00m-2.00m,DeliveryTruck_=>10.00m,
_=>thrownewArgumentException("Notaknownvehicletype",nameof(vehicle))};
簡略類型方式
當前,類型方式需求在類型匹配時聲明一個標識符,即便該標識符是表明放棄的_也能夠,如上面的DeliveryTruck_。而如今你能夠像下面這樣編寫類型:
DeliveryTruck=>10.00m,
聯系方式
C#9.0中引入了與聯系運算符<、<=等相對應的方式。因而,你能夠將上述方式的DeliveryTruck寫成嵌套的switch表達式:
DeliveryTrucktwhent.GrossWeightClassswitch{>5000=>10.00m+5.00m,<3000=>10.00m-2.00m,_=>10.00m,},
此處的>5000和<3000是聯系方式。
邏輯方式
最后,你還能夠將方式與邏輯運算符(and、or和not)組合在一起,它們以英文單詞的方式呈現,以避免與表達式中運用的運算符混淆。例如,上述嵌套的switch表達式能夠按照升序寫成下面這樣:
DeliveryTrucktwhent.GrossWeightClassswitch{<3000=>10.00m-2.00m,>=3000and<=5000=>10.00m,>5000=>10.00m+5.00m,},
中間一行經過and將兩個聯系方式組合到一起,形成了表明間隔的方式。
not方式的常見用法也可應用於null常量方式,比方notnull。例如,咱們能夠依據是否為null來拆分未知狀況的處理辦法:
notnull=>thrownewArgumentException($"Notaknownvehicletype:{vehicle}",nameof(vehicle)),null=>thrownewArgumentNullException(nameof(vehicle))
此外,假如if條件中包括is表達式,那么運用not也很方便,能夠避免笨拙的雙括號:
if(!(eisCustomer)){...}
你能夠這樣寫:
if(eisnotCustomer){...}
改善后的方針類型揣度
“方針類型揣度”指的是表達式從地點的上下文中獲取類型。例如,null和lambda表達式一直是方針類型揣度。
在C#9.0中,有些以前不是方針類型揣度的表達式也能夠經過上下文來判別類型。
支撐方針類型揣度的new表達式
C#中的new表達式一直要求指定類型(隱式類型的數組表達式除外)。現在,假如有清晰的類型能夠分配給表達式,則能夠省去指定類型。
Pointp=new(3,5);
方針類型的??與?:
有時,條件判別表達式中??與?:的各個分支之間並不是很明顯的同一種類型。現在這種狀況會犯錯,但在C#9.0中,假如兩個分支都能夠轉換為方針類型,就沒有問題:
Personperson=student??customer;//Sharedbasetypeint?result=b?0:null;//nullablevaluetype
協變的回來值
有時,咱們需求表明出承繼類中重載的某個辦法的回來類型要比基類中的類型更詳細。C#9.0答應以下寫法:
abstractclassAnimal{publicabstractFoodGetFood();...}classTiger:Animal{publicoverrideMeatGetFood()=>...;}