.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 特性留给你们补充吧,我基本上算是没用过 (┬_┬)。