20

C#9.0 终于来了,您还学的动吗? 带上VS一起解读吧!

 4 years ago
source link: https://segmentfault.com/a/1190000022910419
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

一:背景

1. 讲故事

好消息, .NET 5.0 终于在2020年6月10日发布了第五个预览版,眼尖的同学一定看到了在这个版本中终于支持了 C# 9.0 ,此处有掌声,太好了!!!

.Net5官方链接

2M3qYzr.png!web

可以看到目前的C#9还是预览版,实现了一部分新语法供开发者提前尝鲜,从github的roslyn仓库上可以看到目前准备实现 17 个新特性,现阶段已经实现了 8 个,其中的 In Progress 表示正在开发中。

新特性预览

AbMbIrf.png!web

2. 安装必备

RnI3yi.png!web

找好你自己的vs版本类型哦。。。

二:新特性研究

1. Target-typed new

这个取名一定要留给学易经的大师傅,没见过世面的我不敢造次,取得不佳影响时运,所谓 运去金成铁, 时来铁似金 ,不过大概意思就是说直接new你定义的局部变量的类型,用 issues 中总结的话就是:

Summary: Allow Point p = new (x, y);
Shipped in preview in 16.7p1.

接下来就是全部代码,看看 使用前使用后 的具体差别。

class Program
    {
        static void Main(string[] args)
        {
            //老语法
            var person = new Person("mary", "123456");

            //新语法
            Person person2 = new("mary", "123456");

            Console.WriteLine($"person={person}person2={person2}");
        }
    }

    public class Person
    {
        private string username;
        private string password;

        public Person(string username, string password)
        {
            this.username = username;
            this.password = password;
        }

        public override string ToString()
        {
            return $"username={username},password={password} \n";
        }
    }

7zEBZf3.png!web

然后用ilspy去看看下面的il代码,是不是省略了Person,让自己心里踏实一点。

yiuUn2I.png!web

总的来说这语法还行吧,能起到延长键盘使用寿命的功效。

2. Lambda discard parameters

从字面上看大概就是说可以在lambda上使用取消参数,听起来怪怪的,那本意是什么呢?有时候lambda上的匿名方法签名的参数是不需要的,但在以前必须实打实的定义,这样就会污染方法体,也就是可以在body中被访问,如下图:

3uqa2ir.png!web

但有时候因为客观原因必须使用 Func<int,int,int> 这样的委托,而且还不想让方法签名的参数污染方法体,我猜测在函数式编程中有这样的场景吧,可能有点类似MVC中的 EmptyResult 效果。

好了,我想你大概知道啥意思了,接下来实操一把。。。

Func<int, int, int> func = (_, _) =>
    {
        return 0;
    };

    var result = func(10, 20);

NZjeQnY.png!web

从图中可以看到,我在方法体中是找不到所谓的 _ 变量的,这就神奇了,怎么做到的呢? 带着这个好奇心看看它的IL代码是个什么样子。

.method private hidebysig static 
    void Main (
        string[] args
    ) cil managed 
{
    // Method begins at RVA 0x2048
    // Code size 45 (0x2d)
    .maxstack 3
    .entrypoint
    .locals init (
        [0] class [System.Runtime]System.Func`3<int32, int32, int32> func,
        [1] int32 result
    )

    IL_0000: nop
    IL_0001: ldsfld class [System.Runtime]System.Func`3<int32, int32, int32> ConsoleApp1.Program/'<>c'::'<>9__0_0'
    IL_0006: dup
    IL_0007: brtrue.s IL_0020

    IL_0009: pop
    IL_000a: ldsfld class ConsoleApp1.Program/'<>c' ConsoleApp1.Program/'<>c'::'<>9'
    IL_000f: ldftn instance int32 ConsoleApp1.Program/'<>c'::'<Main>b__0_0'(int32, int32)
    IL_0015: newobj instance void class [System.Runtime]System.Func`3<int32, int32, int32>::.ctor(object, native int)
    IL_001a: dup
    IL_001b: stsfld class [System.Runtime]System.Func`3<int32, int32, int32> ConsoleApp1.Program/'<>c'::'<>9__0_0'

    IL_0020: stloc.0
    IL_0021: ldloc.0
    IL_0022: ldc.i4.s 10
    IL_0024: ldc.i4.s 20
    IL_0026: callvirt instance !2 class [System.Runtime]System.Func`3<int32, int32, int32>::Invoke(!0, !1)
    IL_002b: stloc.1
    IL_002c: ret
} // end of method Program::Main

从上面的IL代码来看 匿名方法 变成了 <>c 类的 <Main>b__0_0 方法,完整签名: ConsoleApp1.Program/'<>c'::'<Main>b__0_0'(int32, int32) ,然后再找一下 <Main>b__0_0 方法的定义。

.class nested private auto ansi sealed serializable beforefieldinit '<>c'
    extends [System.Runtime]System.Object
    .method assembly hidebysig 
        instance int32 '<Main>b__0_0' (
            int32 _,
            int32 _
        ) cil managed 
    {
        // Method begins at RVA 0x2100
        // Code size 7 (0x7)
        .maxstack 1
        .locals init (
            [0] int32
        )

        IL_0000: nop
        IL_0001: ldc.i4.0
        IL_0002: stloc.0
        IL_0003: br.s IL_0005

        IL_0005: ldloc.0
        IL_0006: ret
    } // end of method '<>c'::'<Main>b__0_0'

这说明什么呢? 说明两个参数是真实存在的,但编译器捣了鬼,做了语法上的限制,不让你访问所谓的 _

等等。。。有一个问题,IL中的方法签名怎么是这样的: <Main>b__0_0 (int32 _,int32 _) , 大家应该知道方法签名中不可以出现重复的参数名,比如下面这样定义肯定是报错的。

aaIBz2q.png!web

这说明什么? 说明这个语法糖不仅需要编译器支持,更需要底层的JIT支持,那怎么证明呢?我们用windbg去底层挖一挖。。。为了方便调试,修改如下:

static void Main(string[] args)
        {
            Func<int, int, int> func = (_, _) =>
            {
                Console.WriteLine("进入方法体了!!!");
                Console.ReadLine();
                return 0;
            };

            var result = func(10, 20);
        }

0:000> !clrstack -p
OS Thread Id: 0x52e8 (0)
0000007035F7E5C0 00007ffaff362655 ConsoleApp1.Program+c.b__0_0(Int32, Int32) [C:\5\ConsoleApp1\ConsoleApp1\Program.cs @ 13]
    PARAMETERS:
        this (0x0000007035F7E600) = 0x000001968000cb48
        _ (0x0000007035F7E608) = 0x000000000000000a
        _ (0x0000007035F7E610) = 0x0000000000000014

I7Jzui.png!web

从图中可以看到,虽然都是 _ ,但在线程栈上是完完全全的两个栈地址。 0x0000007035F7E6080x0000007035F7E610

三:总结

总的来说,C#是越来越像函数式编程靠拢,越来越像Scala,就像Jquery的口号一样: Write less,do more。

好了,先就说这两个吧,大家先安装好工具,明天继续解剖~~~


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK