C#基礎拾遺系列之二:使用ILSpy探索C#7.0新增功能點
第一部分:
C#是一種通用的,型別安全的,面向物件的程式語言。有如下特點:
(1)面向物件:c# 是面向物件的範例的一個豐富實現, 它包括封裝、繼承和多型性。C#面向物件的行為包括:
- 統一的型別系統
- 類與介面
- 屬性、方法、事件
(2)型別安全:C#還允許通過dynamic關鍵字動態指定型別。 但是,C#仍然是一個主要的靜態型別語言。之所以是一種強型別的語言,是因為它的型別規則是非常嚴格的,例如,不能夠使用一個float型別的引數去呼叫一個解釋int 型別的函式,除非顯式的把float轉換為int ,這樣有助於防止編碼錯誤。
(3)記憶體管理:C#依賴執行時的自動記憶體管理,它的公共語言執行庫有一個垃圾回收器,在合適非時間回收不再引用的物件所佔的空間,這就釋放了程式設計師手動釋放物件的記憶體。C#並沒有消除指標,它只是使大多數程式設計任務不需要使用指標,對於效能要求高的地方,還是可以使用指標的,但是隻允許在顯式標記為不安全的程式碼塊中使用。
(4)C#和CLR:C#是依賴runtime提供的記憶體管理和異常處理,CLR允許開發者使用不同的語言建立應用程式。C#是被編譯成託管程式碼的幾種託管語言之一。託管程式碼以中間語言或IL表示。CLR把IL轉換成機器的原生代碼,例如X86或X64,通常就在此之前執行,這被稱為即時(Just-In-Time,JIT)編譯。 提前時間編譯也可用於改善大型程式集的啟動時間,或資源受限的裝置。元資料的存在允許,程式集引用其他程式集中的型別而不需要額外的檔案。
(5)CRL和 .Net Framework的關係
.NET Framework由CLR和大量的庫組成;該類庫中包含核心類庫(也就是基礎類庫BCL)和應用類庫,應用類庫又依賴核心類庫
婆婆媽媽說了這麼多,我相信大家都知道,好了,下面我們通過程式碼來看看C#7.0到底有哪些讓你拍手叫好的地方。
第二部分:C#7.0新增的功能
(1)數字字面量的提升:
C#7中的數字文字可以包含下劃線以提高可讀性,這些被稱為數字分隔符,並被編譯器忽略。
程式碼如下:
執行結果:
注意:二進位制文字可以用0b字首指定。
所以見到這種寫法你不要驚訝,只是為了提高可讀性。
(2)Out variables and discards(接收out變數和丟棄out變數)
程式碼:
以前我們的寫法:
現在C#7.0中可以這樣寫:
我們不需要在外面先定義好要接收值的變數,而是直接在裡面寫,是不是程式碼更簡潔,另外一個有趣的地方是,當一個方法要返回多個值的時候,我們可以使用 out _,來選擇性的接收返回來的值,在上面圖中的程式碼中,方法SomeBigMethod返回四個值,但是我們在接收它返回來的值時,可以使用out _不接收返回來的值,而使用out int x,來接收返回來的值,是不是很靈活。
程式碼執行結果如下:
ILSpy結果:
// Methods .method private hidebysig static void Main ( string[] args ) cil managed { // Method begins at RVA 0x2050 // Code size 49 (0x31) .maxstack 4 .entrypoint .locals init ( [0] int32, [1] bool, [2] int32, [3] int32, [4] int32, [5] int32 ) // (no C# code) IL_0000: nop // bool successful = int.TryParse("123", out result); IL_0001: ldstr "123" IL_0006: ldloca.s 0 IL_0008: call bool [System.Runtime]System.Int32::TryParse(string, int32&) IL_000d: stloc.1 // SomeBigMethod(out int _, out int _, out int x, out int _); IL_000e: ldloca.s 3 IL_0010: ldloca.s 4 IL_0012: ldloca.s 2 IL_0014: ldloca.s 5 IL_0016: call void ConsoleApp1.Program::SomeBigMethod(int32&, int32&, int32&, int32&) // (no C# code) IL_001b: nop // Console.WriteLine(x); IL_001c: ldloc.2 IL_001d: call void [System.Console]System.Console::WriteLine(int32) // (no C# code) IL_0022: nop // Console.WriteLine(result); IL_0023: ldloc.0 IL_0024: call void [System.Console]System.Console::WriteLine(int32) // (no C# code) IL_0029: nop // Console.ReadKey(); IL_002a: call valuetype [System.Console]System.ConsoleKeyInfo [System.Console]System.Console::ReadKey() IL_002f: pop // (no C# code) IL_0030: ret } // end of method Program::Main
(3)Patterns
作用:你可以使用is運算子來引入一個變數,這個變數被稱為模式變數。不明白,看個例子就明白了。
程式碼如下:
解析:x is string s 的作用是:如果x 可以被轉換為string 轉換後的值賦值給了s ,所以輸出的結果就是字串的長度。
其中switch的宣告也支援這種模式,而且還可以使用when子句指定條件,程式碼如下:
執行結果:
解析:Foo2(9)傳遞過來的是9,是int 型別,所以就進入到第一個case 子句中,所以最終輸出的結果就是:It is an int !,這個解釋給零分,下面我們通過ILSpy看看這種語法糖到底是什麼東東,如下圖所示:
我就不解釋了,大家一看就明白,是不是想拍下大腿,TM原來就這麼簡單!!!
(4)本地方法(Local methods)
作用:A local method is a method declared inside another function。這裡我給出英文,因為這種方式給出是最準確的,中文翻譯出來就TM看不懂了。
執行結果:
解析: 定義了一個本地方法,返回值型別是int 傳入的引數是value ,返回值是:value*value*value+i
Cube(2),呼叫傳入值2 ,所以最終計算出來的值為 2*2*2+9=17
注意:本地方法僅對包含函式可見,並且可以使用包含該本地方法的變數。
ILSpy反編譯的結果:
可以看出在呼叫Cube(2),最終被編譯成Cube(2,ref xx)這樣一個方法,但看不到 <WriteCubes>g__Cube|3_0方法的內部實現。
(5)c# 6 介紹了方法的 "fat-箭頭" 語法, 可以用在只讀、屬性、運算子和索引器。c# 7 將此擴充套件到建構函式、讀/寫屬性、終結器
程式碼:
ILSPy程式碼結果:
(6)對於 c# 7, 可能最顯著的改進是顯式元組支援
作用:元組提供了一種簡單的方法來儲存一組相關值
程式碼:
執行結果:
解析: var bob = ("Bob", 23);定義了一個元組,可以使用bob.Item1來訪問第一個引數,可以使用bob.Item2來訪問第二引數,但問題來了,為什麼可以這樣來訪問???
ILSpy結果:
可以看到,元組其實是一個ValueTuple<,>的泛型型別,其中string int 是有你的值的型別決定的,那為什麼可以使用Item1和Item2來訪問對應的值呢?
首先Item1和Item2是人家 ValueTuple<T1, T2> 中定義的,那為什麼我訪問Item1就是"Bob",那是因為在建構函式中,把"Bob"賦值給了Item1,所以明白了吧。
另外可以看出元組是一個結構體,屬於值型別的。講到這裡還沒有講完元組的點,由於編譯器的魔力, 元組元素可以被命名為下面的形式:
ILSpy結果:
藉助於元組,函式可以返回多個引數,而不需要藉助於out 引數:
執行結果:
ILSpy結果:
注意:元組隱含地支援反解析模式, 因此它們可以很容易地被分解成單個變數。我們可以重寫前面的主方法使 GetFilePosition 返回的元組被分配給兩個區域性變數:row和cloum:
執行結果:
ILSPy結果:(結果和上面的一樣)
好了,元組就講到這裡,接下讓我們看看如何丟擲異常。
(7)丟擲異常
功能:在C#7之前,throw總是要被宣告,現在它可以作為一個表示式出現在一個函式 體中,而且也可以出現在三元表示式中。
ILSpy結果:
(8)字串的插值
直接上程式碼:
如果要多行顯示,可以這樣寫:
注意:$符一定要在@符號之前。
ILSpy結果:
簡單我就不多說了,繼續下面的知識點。
(9)異常篩選器(Exception filters)
作用:允許你在catch中應用一個條件。
(10)引用本地變數Ref Locals
作用:C#7.0中引入了一個極為重要的點,藉此,你可以定義一個本地變數,這個變數引用一個數組中的元素或者物件中的欄位。
程式碼:
注意:Ref Locals 必須是陣列中的一個元素、欄位、或者本地變數,不能是屬性。它通常與 ref returns 一起使用。
執行結果:
解析:ref int age 標註這個變數時就是一個引用型別的變數。
(11)Ref Returns
作用:你可以在一個方法中返回一個 ref local,這種方式被叫做ref return
程式碼:
執行結果:
解析:private static ref int GetX() 其實是一個 返回值為int32&(就是一個標記了記憶體指標的INT32型別)的方法,也就是返回一個地址,這樣我再修改值後其實就是修改的x的值。
ILSpy結果:
注意:ldsflda int32 :是把一個靜態欄位x的地址壓入到棧中,ret,然後返回,在Main方法中,呼叫上面的方法後,從棧頂把值取出來,儲存到本地變數列表中索引位置0裡面。
然後取本地變數中索引位置為0的值,並壓入棧中,注意重點來了,stind.i4 是把 ldc.i4.s 9 值 的地址儲存下來,這樣就改變了x的值。所以這個int32&其實就是一個變數的地址,也就是我們通常所說的指標。
好了講到這裡基本上C#7.0新增的功能就講的差不多了,後續我會繼續補充C#7.0新的知識點,希望對你有幫助!謝謝。
最後,歡迎大家加入到我的C#+.Net Core英文書籍翻譯群,我會不定期通過部落格更新翻譯的英文資料,希望得到最新的C#知識,同時對你我也有所提高。
補充:C#7.1 C#7.2 C#8.0
C# 7.1
2017年8月c# 7.1 釋出了 , 作為Visual Studio 2017(15.3 版 ) 更新的一部分。不同於新語言的釋出,這一次釋出的新功能不會因為你更新了Visual Studio 2017而啟用,即不在現有專案中,也不在建立的新專案中出現。
如果我們想嘗試C#7.1的新特性,生成時的報錯資訊會提示我們升級語言的版本:
注意:如果你已經保證Visual Studio 2017(15.3 版 ) 是最新版本的話,我們可以通過下面的方法來更改預設C#的版本,在控制檯專案右鍵,選擇“ 屬性”,如下圖:
注意這裡要強調一點:預設情況下C#7.1是沒有被選中的,這是為了開發團隊能夠更好的控制使用次要版本的語言,如果新的語言特性自動可用,這將強制團隊一開始用這些新特性的時候,就要立馬更新IDE,當一個新特性第一次被應用在專案中,它是不會被編譯的。我們所選的語言版本是被儲存在專案中的,是一個特定的專案,而且需要特定的配置。因此當我們再專案屬性中改變語言的版本時,你要確保它對所有的配置都適用。我們可以如下配置:
選擇所有配置。如果你僅僅選擇了語言的版本和Debug配置,那麼會造成在release下是生產失敗。
對於某些語言功能, VS2017還有一個可用的程式碼修復功能 ,它將更改語言版本到
7.1 或最新的次要版本,在設定了all configurations配置的情況下。
好了,下面讓我們利用ILSpy繼續探索C#7.1的新特性。
(1)Async Main
在C#7.0中就有考慮過,支援非同步的Main函式,但是被擱置了直到C#7.1才出現,它的出現可以使控制檯應用程式中的Main函式,可以使用async 和 await 語法。在C#7.1之前,在 c# 7.1 之前, 主方法作為程式入口點支援以下內容:
public static void Main();
public static int Main();
public static void Main(string[] args);
public static int Main(string[] args);
當一個方法的內部呼叫另外一個非同步方法的時候,使用了await時,這個方法就要使用async ,也就是說兩個需要同時出現,雖然變通方法是編寫多行樣板程式碼,但是這樣的模式依賴於對方法的非正常使用,難於理解。例如:
static void Main(string[] args)
{
MainAsync(args).GetAwaiter().GetResult();
}
static async Task MainAsync(string[] args)
{
// asynchronous code
}
在C#7.1中對於非同步的Main方法支援額外的簽名:
public static Task Main();
public static Task Main();
public static Task Main(string[] args);
public static Task Main(string[] args);
當使用上面這些簽名時,可以在非同步的方法中直接寫async,編譯器將會生產對應的樣板程式碼,使他們工作。
程式碼:
(2)Default Literal Expressions
作用:預設值表示式可用於返回給定型別的預設值。
C#7.1之前的寫法:
特別是在用泛型型別時, 我們不知道返回什麼樣的值,可以這樣做:
C#7.1中的寫法:
(3)Inferred Tuple Element Names(推斷元組元素名稱)
元組首先是在C#7.0中引入的,C#7.1進行了輕微的改動,當在建立元組的時候必須顯示的指定名稱,否則元素只能通過預設名稱 Item1、Item2 等:
在 c# 7.1 中, 可以從用於構造元組的變數的名稱推斷出元組名稱。程式碼如下:
ILSpy結果:(不解釋了)
(4)Generic Pattern Matching
作用:c# 7.0 中最重要的新功能之一是使用 "is" 關鍵字進行模式匹配和switch語句,型別模式允許我們基於值型別進行分支,但是這對泛型的型別不起作用,下面的程式碼在C#7.0中是不能編譯成功的。
void Attack<T>(T weapon, IEnemy enemy) where T : IWeapon
{
switch (weapon)
{
case Sword sword:
// process sword attack
break;
case Bow bow:
// process bow attack
break;
}
}
c# 7.1 擴充套件型別模式以支援泛型型別, 從而使程式碼有效。
C#7.2
語言的發展並沒有隨著 c# 7.1 的釋出而停止。該團隊已經在工作下一次要版本7.2具體的釋出日期並沒有宣佈,儘管這些他們公開談過,但是這些新的功能並不是很好用。目前為C#7.2計劃的幾種語言新功能,仍然會受到改進,其中有些可能會推遲到更高版本,還有可能會新增新的功能。好了接下來讓我們繼續看看C#7.2中有哪些新的功能。
(1)基本說明符後的數字分隔符
C#7.0
程式碼:
C#7.2 允許使用分隔符(0x_ 0b_)
程式碼:
(2)Non-trailing Named Arguments
命名引數是在C#4.0中新增進來的,它們主要是允許可選引數在呼叫方法時可以跳過某些引數, 但對於後面的所有引數,必須使用命名引數, 以便編譯器能夠匹配它們。
程式碼:
如果一個引數不是可選引數,引數仍然可以使用命名引數以提高程式碼的可讀性,並且使用命名引數前提下,可以改變參入引數的位置。
程式碼:
但是, c# 還不允許位置引數在同一個方法呼叫中跟隨命名引數:
程式碼:
C#7.2
但是出於程式碼的可讀性,最好還是使用下面這種方式(別讓編譯器寵壞你):
(3)Conditional Ref Operator(條件Ref運算子)
程式碼:
C#8.0(簡單瞭解即可,知道有這個點就行了)
(1)Recursive Patterns(遞迴模式)
在前在C#7.0中也講到了模式的問題,在C#8.0中有進一步的支援,遞迴模式是計劃被新增到其中的一種模式,他們將會允許部分資料匹配子模式。
(2)Default Interface Methods(預設的介面方法)
(3)Nullable Reference Types(可空的引用型別)
參考書籍:《C 7.0 in a Nutshell 7th Edition》
關於C#6.0的可以參考這篇文章:
敏捷的水:http://www.cnblogs.com/cnblogsfans/p/5086292.html
作者:郭崢
出處:http://www.cnblogs.com/runningsmallguo/
本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連結。