.NET 5 終於在 6月25日 發佈了第六個預覽版,隨之而來的是更多的新特性加入到了 C# 9 Preview 中,這個系列也能夠繼續往下寫了,廢話很少說,今天來看一下 Top-level programs
和 Extending Partial Methods
兩大新特性。python
下載最新的 .net 5 preview 6
。ide
下載最新的 Visual Studio 2019 version 16.7 Preview 3.1
函數
若是你們玩過 python,應該知道在 xxx.py 中寫一句 print,這程序就能跑起來了,簡單高效又粗暴,很開心的是這特性被帶到了C# 9.0 中。測試
using System; namespace ConsoleApp2 { class Program { static void Main(string[] args) { Console.WriteLine("Hello World!"); } }
System.Console.WriteLine("Hello World!");
這就有意思了,Main入口函數去哪了? 沒它的話,JIT還怎麼編譯代碼呢? 想知道答案的話用 ILSpy 反編譯看一下就好啦!spa
.class private auto ansi abstract sealed beforefieldinit $Program extends [System.Runtime]System.Object { .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) // Methods .method private hidebysig static void $Main ( string[] args ) cil managed { // Method begins at RVA 0x2050 // Code size 18 (0x12) .maxstack 8 .entrypoint IL_0000: ldstr "Hello World!" IL_0005: call void [System.Console]System.Console::WriteLine(string) IL_000a: nop IL_000b: call string [System.Console]System.Console::ReadLine() IL_0010: pop IL_0011: ret } // end of method $Program::$Main } // end of class $Program
從 IL 上看,類變成了 $Program
, 入口方法變成了 $Main
, 這就好玩了,在咱們的印象中入口函數必須是 Main
,不然編譯器會給你一個大大的錯誤,你加了一個 $ 符號,那CLR還能認識嗎? 能不能認識咱們用 windbg 看一些託管和非託管堆棧,看看有什麼新發現。.net
0:010> ~0s ntdll!NtReadFile+0x14: 00007ffe`f8f8aa64 c3 ret 0:000> !dumpstack OS Thread Id: 0x7278 (0) Current frame: ntdll!NtReadFile + 0x14 Child-SP RetAddr Caller, Callee 0000008551F7E810 00007ffed1e841dc (MethodDesc 00007ffe4020d500 + 0x1c System.Console.ReadLine()), calling 00007ffe400ab090 0000008551F7E840 00007ffe4014244a (MethodDesc 00007ffe401e58f0 + 0x3a $Program.$Main(System.String[])), calling 00007ffe40240f58 0000008551F7E880 00007ffe9fcc8b43 coreclr!CallDescrWorkerInternal + 0x83 [F:\workspace\_work\1\s\src\coreclr\src\vm\amd64\CallDescrWorkerAMD64.asm:101] 0000008551F7E8C0 00007ffe9fbd1e03 coreclr!MethodDescCallSite::CallTargetWorker + 0x263 [F:\workspace\_work\1\s\src\coreclr\src\vm\callhelpers.cpp:554], calling coreclr!CallDescrWorkerWithHandler [F:\workspace\_work\1\s\src\coreclr\src\vm\callhelpers.cpp:56] 0000008551F7E950 00007ffe9fb8c4e5 coreclr!MethodDesc::IsVoid + 0x21 [F:\workspace\_work\1\s\src\coreclr\src\vm\method.cpp:1098], calling coreclr!MetaSig::IsReturnTypeVoid [F:\workspace\_work\1\s\src\coreclr\src\vm\siginfo.cpp:5189] 0000008551F7EA00 00007ffe9fb8c4bf coreclr!RunMainInternal + 0x11f [F:\workspace\_work\1\s\src\coreclr\src\vm\assembly.cpp:1488], calling coreclr!MethodDescCallSite::CallTargetWorker [F:\workspace\_work\1\s\src\coreclr\src\vm\callhelpers.cpp:266] 0000008551F7EB30 00007ffe9fb8c30a coreclr!RunMain + 0xd2 [F:\workspace\_work\1\s\src\coreclr\src\vm\assembly.cpp:1559], calling coreclr!RunMainInternal [F:\workspace\_work\1\s\src\coreclr\src\vm\assembly.cpp:1459]
從上面堆棧的流程圖看: coreclr!RunMain
-> coreclr!MethodDesc
-> coreclr!CallDescrWorkerInternal
-> $Program.$Main
, 確實被調用了,不過有一個重大發現,在 $Program.$Main
調用以前底層的 CLR 讀取了 方法描述符,這就是一個重大突破點,方法描述符在哪裏呢? 能夠用 ildasm 去看一下元數據列表。翻譯
能夠看到,入口函數那裏打上了一個 ENTRYPOINT
標記,這就說明入口函數名實際上是能夠隨便更改的,只要被 ENTRYPOINT
打上標記便可,CoreCLR就能認的出來~~~code
咱們知道 部分方法 是一個很好的樁函數,並且在 C# 3.0 中就已經實現了,那時候給咱們增長了不少限制,以下圖:blog
翻譯過來就是:ci
在 C# 9.0 中放開了對 方法簽名 的全部限制,正如 issue 總結:
這是一個很是好的消息,如今你的部分方法上能夠加上各類類型的返回值啦,這裏我舉一個例子:
class Program { static void Main(string[] args) { var person = new Person(); Console.WriteLine(person.Run("jack")); } } public partial class Person { public partial string Run(string name); } public partial class Person { public partial string Run(string name) => $"{name}:開溜了~"; }
而後咱們用 ILSpy 簡單看看底層怎麼玩的,以下圖能夠看到其實就是一個簡單的合成,對吧。
如今我有想法了,若是我不給 Run 方法實現會怎麼樣? 把下面的 partial 類註釋掉看一下。
從報錯信息看,可訪問的修飾符必需要有方法實現,<font color="red">還覺得直接編譯的時候抹掉呢。</font> 這就起不到樁函數的做用:-D,不過這個特性仍是給了咱們更多的可能用的到的應用場景吧。
本篇兩個特性仍是很是實用的,Top-level programs 讓咱們能夠寫更少的代碼,甚至拿起 記事本 均可以快捷的編寫相似一次性使用的測試代碼, Partial Methods 特性留給你們補充吧,我基本上算是沒用過 (┬_┬)。