天天看點

C#文法特性總結

C# 10已與.NET 6、VS2022一起釋出,本文按照.NET的釋出順序,根據微軟官方文檔整理C#中一些有趣的文法特性。

注:基于不同.NET平台建立的項目,預設支援的C#版本是不一樣的。下面介紹的文法特性,會說明引入C#的版本,在使用過程中,需要注意使用C#的版本是否支援對應的特性。C#語言版本控制,可參考官方文檔。

匿名函數是C# 2推出的功能,顧名思義,匿名函數隻有方法體,沒有名稱。匿名函數使用delegate建立,可轉換為委托。匿名函數不需要指定傳回值類型,它會根據return語句自動判斷傳回值類型。

注:C# 3後推出了lambda表達式,使用lambda可以以更簡潔的方式建立匿名函數,應盡量使用lambda來建立匿名函數。與lambda不同的是,使用delegate建立匿名函數可以省略參數清單,可将其轉換為具有任何參數清單的委托類型。

從C# 3開始,當屬性通路器中不需要其它邏輯時,可以使用自動屬性,以更簡潔的方式聲明屬性。編譯時,編譯器會為其建立一個僅可以通過get、set通路器通路的私有、匿名字段。使用VS開發時,可以通過snippet代碼片段prop+2次tab快速生成自動屬性。

另外,在C# 6以後,可以初始化自動屬性:

匿名類型是C# 3後推出的功能,它無需顯示定義類型,将一組隻讀屬性封裝到單個對象中。編譯器會自動推斷匿名類型的每個屬性的類型,并生成類型名稱。從CLR的角度看,匿名類型與其它引用類型沒什麼差別,匿名類型直接派生自object。如果兩個或多個匿名對象指定了順序、名稱、類型相同的屬性,編譯器會把它們視為相同類型的執行個體。在建立匿名類型時,如果不指定成員名稱,編譯器會把用于初始化屬性的名稱作為屬性名稱。

匿名類型多用于LINQ查詢的select查詢表達式。匿名類型使用new與初始化清單建立:

C# 3推出了殺手锏功能,查詢表達式,即語言內建查詢(LINQ)。查詢表達式以查詢文法表示查詢,由一組類似SQL的文法編寫的子句組成。

查詢表達式必須以from子句開頭,必須以select或group子句結尾。在第一個from子句與最後一個select或group子句之間,可以包含:where、orderby、join、let、其它from子句等。

可以為SQL資料庫、XML文檔、ADO.NET資料集及實作了IEnumerable或IEnumerable接口的集合對象進行LINQ查詢。

完整的查詢包括建立資料源、定義查詢表達式、執行查詢。查詢表達式變量是存儲查詢而不是查詢結果,隻有在循環通路查詢變量後,才會執行查詢。

可使用查詢文法表示的任何查詢都可以使用方法表示,建議使用更易讀的查詢文法。有些查詢操作(如 Count 或 Max)沒有等效的查詢表達式子句,必須使用方法調用。 可以結合使用方法調用和查詢文法。

關于LINQ的詳細文檔,參見微軟官方文檔

C# 3推出了很多強大的功能,如自動屬性、擴充方法、隐式類型、LINQ,以及Lambda表達式。

建立Lambda表達式,需要在 => 左側指定輸入參數(空括号指定零個參數,一個參數可以省略括号),右側指定表達式或語句塊(通常兩三條語句)。任何Lambda表達式都可以轉換為委托類型,表達式Lambda語句還可以轉換為表達式樹(語句Lambda不可以)。

匿名函數可以省略參數清單,Lambda中不使用的參數可以使用棄元指定(C# 9)。

使用async和await,可以建立包含異步處理的Lambda表達式和語句(C# 5)。

從C# 10開始,當編譯器無法推斷傳回類型時,可以在參數前面指定Lambda表達式的傳回類型,此時參數必須加括号。

擴充方法也是C# 3推出的功能,它能夠向現有類型添加方法,且無需修改原始類型。擴充方法是一種靜态方法,不過是通過執行個體對象文法進行調用,它的第一個參數指定方法操作的類型,用this修飾。編譯器在編譯為IL時會轉換為靜态方法的調用。

如果類型中具有與擴充方法相同名稱和簽名的方法,則編譯器會選擇類型中的方法。編譯器進行方法調用時,會先在該類型的的執行個體方法中尋找,找不到再去搜尋該類型的擴充方法。

最常見的擴充方法是LINQ,它将查詢功能添加到現有的System.Collections.IEnumerable和System.Collections.Generic.IEnumerable類型中。

為struct添加擴充方法時,由于是值傳遞,隻能對struct對象的副本進行更改。從C# 7.2開始,可以為第一個參數添加ref修飾以進行引用傳遞,這樣就可以對struct對象本身進行修改了。

從C# 3開始,在方法範圍内可以聲明隐式類型變量(var)。隐式類型為強類型,由編譯器決定類型。

var常用于調用構造函數建立對象執行個體時,從C# 9開始,這種場景也可以使用确定類型的new表達式:

注:當傳回匿名類型時,隻能使用var。

從C# 3開始,可以在單條語句中執行個體化對象或集合并執行成員配置設定。

使用對象初始化清單,可以在建立對象時向對象的任何可通路字段或屬性配置設定值,可以指定構造函數參數或忽略參數以及括号。

從C# 6開始,對象初始化清單不僅可以初始化可通路字段和屬性,還可以設定索引器。

集合初始化清單可以指定一個或多個初始值:

.NET Framework 3.5/4.0,分别提供了内置的Action和Func泛型委托類型。void傳回類型的委托可以使用Action類型,Action的變體最多有16個參數。有傳回值類型的委托可以使用Func類型,Func類型的變體最多同樣16個參數,傳回類型為Func聲明中的最後一個類型參數。

C# 4主要的功能就是引入了dynamic關鍵字。dynamic類型在變量使用及其成員引用時會繞過編譯時類型檢查,在運作時再進行解析。這便實作了與動态類型語言(如JavaScript)類似的構造。

C# 4引入了命名參數和可選參數。命名參數可為形參指定實參,方式是指定比對的實參與形參,這時無需比對參數清單中的位置。可選參數通過指定參數預設值,可以省略實參。可選參數需位于參數清單末尾,如果為一系列可選參數中的任意一個提供了實參,則必須為該參數前面的所有可選參數提供實參。

也可以使用OptionalAttribute特性聲明可選參數,此時無需為形參提供預設值。

C# 6中推出了靜态導入功能,使用using static指令導入類型,可以無需指定類型名稱即可通路其靜态成員和嵌套類型,這樣避免了重複輸入類型名稱導緻的晦澀代碼。

從C# 6開始,when可用于catch語句中,用來指定為執行特定異常處理程式必須為true的條件表達式,當表達式為false時,則不會執行異常處理。

C# 6開始,可以為自動屬性指定初始化值以使用類型預設值以外的值:

從C# 6起,支援方法、運算符和隻讀屬性的表達式體定義,自C# 7.0起,支援構造函數、終結器、屬性、索引器的表達式體定義。

C# 6起,推出了null條件運算符,僅當操作數的計算結果為非null時,null條件運算符才會将成員通路?.或元素通路?[]運算應用于其操作數;否則,将傳回null。

從C# 6開始,可以使用$在字元串中插入表達式,使代碼可讀性更高也降低了字元串拼接出錯的機率。如果在内插字元串中包含大括号,需使用兩個大括号("{{"或""}}")。如果内插表達式需使用條件運算符,需要将其放在括号内。從C# 8起,可以使用$@"..."或@$"..."形式的内插逐字字元串,在此之前的版本,必須使用$@"..."形式。

C# 6提供了nameof表達式,nameof可生成變量、類型或成員名稱(非完全限定)作為字元串常量。

C# 7.0中對out文法進行了改進,可以直接在方法調用的參數清單中聲明out變量,無需再單獨編寫一條聲明語句:

C# 7.0中引入了對元組的語言支援(之前版本也有元組但效率低下),可以使用元組表示包含多個資料的簡單結構,無需再專門寫一個class或struct。元組是值類型的,是包含多個公共字段以表示資料成員的輕量級資料結構,無法為其定義方法。C# 7.3後元組支援==與!=。

當某方法傳回元組時,如需提取元組成員,可通過為元組的每個值聲明單獨的變量來實作,稱為解構元組。使用元組作為方法傳回類型,可以替代定義out方法參數。

從C# 7.0開始支援棄元,棄元是占位符變量,相當于未指派的變量,表示不想使用該變量,使用下劃線_表示棄元變量。如下列舉了一些棄元的使用場景:

C# 7.0添加了模式比對功能,之後每個主要C#版本都擴充了模式比對功能。模式比對用來測試表達式是否具有某些特征,is表達式、switch語句和switch表達式均支援模式比對,可使用when關鍵字來指定模式的其他規則。

模式比對目前包含這些類型:聲明模式、類型模式、常量模式、關系模式、邏輯模式、屬性模式、位置模式、var模式、棄元模式,詳細内容可參考官方文檔。

is模式表達式改進了is運算符功能,可在一條指令配置設定結果:

預設值表達式生成類型的預設值,之前版本僅支援default運算符,C# 7.1後增強了default表達式的功能,當編譯器可以推斷表達式類型時,可以使用default生成類型的預設值。

從C# 8開始,可以使用switch表達式。switch表達式相較于switch語句的改進之處在于:

變量在switch關鍵字之前;

使用<code>=&gt;</code>替換<code>case :</code>結構;

使用棄元_替換default運算符;

使用表達式替換語句。

C# 8添加了using聲明功能,它訓示編譯器聲明的變量應在代碼塊的末尾進行處理。 using聲明相比傳統的using語句代碼更簡潔,這兩種寫法都會使編譯器在代碼塊末尾調用Dispose()。

C# 8中添加了索引和範圍功能,為通路序列中的單個元素或範圍提供了簡潔的文法。該文法依賴兩個新類型與兩個新運算符:

System.Index表示一個序列索引;

System.Range表示序列的子範圍;

末尾運算符<code>^</code>,使用該運算符加數字,指定倒數第幾個;

範圍運算符<code>..</code>,指定範圍的開始和末尾。

範圍運算符包括此範圍的開始,但不包括此範圍的末尾。

??合并運算符:C# 6後可用,如果左操作數的值不為null,則??傳回該值;否則,它會計算右操作數并傳回其結果。如果左操作數的計算結果為非null,則不會計算其右操作數。

??=合并指派運算符:C# 8後可用,僅在左側操作數的求值結果為null時,才将右操作數的值指派給左操作數。否則,不會計算其右操作數。??=運算符的左操作數必須是變量、屬性或索引器元素。

C# 9推出了頂級語句,它從應用程式中删除了不必要的流程,應用程式中隻有一個檔案可使用頂級語句。頂級語句使主程式更易讀,減少了不必要的模式:命名空間、class Program和static void Main()。

使用VS建立指令行項目,選擇.NET 5及以上版本,就會使用頂級語句。

C# 10添加了global using指令,當關鍵字global出現在using指令之前時,該using适用于整個項目,這樣可以減少每個檔案using指令的行數。global using 指令可以出現在任何源代碼檔案的開頭,但需添加在非全局using之前。

global修飾符可以與static修飾符一起使用,也可以應用于using别名指令。在這兩種情況下,指令的作用域都是目前編譯中的所有檔案。

C# 10引入了檔案範圍的命名空間,可将命名空間包含為語句,後加分号且無需添加大括号。一個代碼檔案通常隻包含一個命名空間,這樣簡化了代碼且消除了一層嵌套。檔案範圍的命名空間不能聲明嵌套的命名空間或第二個檔案範圍的命名空間,且它必須在聲明任何類型之前,該檔案内的所有類型都屬于該命名空間。

C# 9開始引入了with表達式,它使用修改的特定屬性和字段生成其操作對象的副本,未修改的值将保留與原對象相同的值。對于引用類型成員,在複制操作數時僅複制對該成員執行個體的引用,with表達式生成的副本和原對象都具有對同一引用類型執行個體的通路權限。

在C# 9中,with表達式的左操作數必須為record類型,C# 10進行了改進,with表達式的左操作數也可以是struct類型。

轉載請注明出處,歡迎交流。