zoukankan      html  css  js  c++  java
  • C# 9.0 终于来了, Top-level programs 和 Partial Methods 两大新特性探究

    一:背景

    1. 讲故事

    .NET 5 终于在 6月25日 发布了第六个预览版,随之而来的是更多的新特性加入到了 C# 9 Preview 中,这个系列也可以继续往下写了,废话不多说,今天来看一下 Top-level programsExtending Partial Methods 两大新特性。

    2. 安装必备

    下载最新的 .net 5 preview 6

    下载最新的 Visual Studio 2019 version 16.7 Preview 3.1

    二:新特性研究

    1. Top-level programs

    如果大家玩过 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 反编译看一下就好啦!

    
    .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 看一些托管和非托管堆栈,看看有什么新发现。

    
    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\_work1ssrccoreclrsrcvmamd64CallDescrWorkerAMD64.asm:101]
    0000008551F7E8C0 00007ffe9fbd1e03 coreclr!MethodDescCallSite::CallTargetWorker + 0x263 [F:workspace\_work1ssrccoreclrsrcvmcallhelpers.cpp:554], calling coreclr!CallDescrWorkerWithHandler [F:workspace\_work1ssrccoreclrsrcvmcallhelpers.cpp:56]
    0000008551F7E950 00007ffe9fb8c4e5 coreclr!MethodDesc::IsVoid + 0x21 [F:workspace\_work1ssrccoreclrsrcvmmethod.cpp:1098], calling coreclr!MetaSig::IsReturnTypeVoid [F:workspace\_work1ssrccoreclrsrcvmsiginfo.cpp:5189]
    0000008551F7EA00 00007ffe9fb8c4bf coreclr!RunMainInternal + 0x11f [F:workspace\_work1ssrccoreclrsrcvmassembly.cpp:1488], calling coreclr!MethodDescCallSite::CallTargetWorker [F:workspace\_work1ssrccoreclrsrcvmcallhelpers.cpp:266]
    0000008551F7EB30 00007ffe9fb8c30a coreclr!RunMain + 0xd2 [F:workspace\_work1ssrccoreclrsrcvmassembly.cpp:1559], calling coreclr!RunMainInternal [F:workspace\_work1ssrccoreclrsrcvmassembly.cpp:1459]
    
    

    从上面堆栈的流程图看: coreclr!RunMain -> coreclr!MethodDesc -> coreclr!CallDescrWorkerInternal -> $Program.$Main, 确实被调用了,不过有一个重大发现,在 $Program.$Main 调用之前底层的 CLR 读取了 方法描述符,这就是一个重大突破点,方法描述符在哪里呢? 可以用 ildasm 去看一下元数据列表。

    可以看到,入口函数那里打上了一个 ENTRYPOINT 标记,这就说明入口函数名其实是可以随便更改的,只要被 ENTRYPOINT打上标记即可,CoreCLR就能认的出来~~~

    2. Partial Methods

    我们知道 部分方法 是一个很好的桩函数,而且在 C# 3.0 中就已经实现了,那时候给我们增加了很多限制,如下图:

    翻译过来就是:

    • 部分方法的签名必须一致

    • 方法必须返回void

    • 不允许使用访问修饰符,而且还是隐式私有的。

    在 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 类注释掉看一下。

    从报错信息看,可访问的修饰符必须要有方法实现,还以为直接编译的时候抹掉呢。 这就起不到桩函数的作用:-D,不过这个特性还是给了我们更多的可能用的到的应用场景吧。

    三:总结

    本篇两个特性还是非常实用的,Top-level programs 让我们可以写更少的代码,甚至拿起 记事本 都可以快捷的编写类似一次性使用的测试代码, Partial Methods 特性留给大家补充吧,我基本上算是没用过 (┬_┬)。

    如您有更多问题与我互动,扫描下方进来吧~

    图片名称
  • 相关阅读:
    斐波那契数列
    进制转换为伪代码
    python模拟进程
    第十一周学习总结
    第十周学习总结
    第九周学习总结
    俄罗斯方块
    熟悉编程语言
    小学四则运算
    第八周学习总结
  • 原文地址:https://www.cnblogs.com/huangxincheng/p/13197862.html
Copyright © 2011-2022 走看看