欢迎访问杏彩注册_杏彩体育(中国)官方网站网站!

中国繁體中文
图片名

全国订购热线:
0833-2416300

活动公告 公司新闻 健身指南 器材保养 常见问题

杏彩官网登录C 9 特性一览及评价

作者:杏彩体育管理员 发布时间:2024-04-03 21次浏览

  杏彩官网登录C 9 特性一览及评价C# 最近又诞生了比较多的特性(feature),这些特性确实大大简化了我们编码的代码量,但有一些东西改变了我们对 C# 的认知。下面我们来看看它们。

  我们会使用new关键字来实例化,但在部分字段和属性声明的时候,这些类型已经是在旁边给出,且不能使用var代替的。因此,我们必须这么写:

  例如上面的类似写法。可以看到,实例化里必须要写上类型,但这个类型名称在上下文里已经给出,所以不必去写应该也知道,但 C# 一直没考虑省略的问题。直到 C# 9,这个特性才提上日程:

  有些对象它会自带get和set的属性,以便初始化可以直接使用。此时我们可以将对象省略初始化类型的形式写出:

  这两种写法里,后者更加清晰流畅,但需要注意的是,后者new后的小括号不可以省略,否则这玩意儿就被编译器看成是匿名类型了,因此会报错。

  这个特性不属于 C# 语法的特性,它是为的 API 里添加了一个新的特性(attribute):SkipLocalsInitAttribute,它用来告诉当前代码块里,所有的变量都在定义的时候能不初始化的地方都不初始化,以便优化代码执行的效率。

  弃元这个概念从 C# 7 开始就有了,它用来表示一个特殊的变量,该变量在不使用的时候就写成关键字_来表示它是一个弃元,放弃对这个数值的使用:。

  C# 9 开始允许传入 Lambda 表达式的时候,杏彩平台app参数不使用时可用_替代。当且仅当参数同时有两个及以上的都不用的话,弃元才生效:

  在上面这个情景下的时候,Lambda 弃元会比较有用——对一个控件赋值一个事件处理方法,且该方法直接用 Lambda 表达式赋值的时候。

  但刚才说到,当且仅当至少两个变量弃元时,才生效。如果 Lambda 只需要一个参数的时候,即使你写_,它也是一个正常的变量:

  C# 提供了两个新的关键字nint和nuint用来表达底层平台的int不确定(随机器位数大小变化的int类型)。

  因为 C# 的int和uint都是固定的 4 字节数据,所以是不可变的。当我们不得不转换到底层(调用底层函数)的时候,我们的写类似于下面这样:

  这样很丑,对吧。特别是要规定参数的转换类型,是特别糟糕的。为了区分平台,我们还不得不去用MarshalAsAttribute来给参数做一个转换。稍不注意写错了,还要直接运行时报错。比如你用不好 C/C++ 底层的 LPSTR 和 LPWSTR,不知道区别的时候,你在这里随意赋值,绝对会引起错误。

  C# 为了我们安全和快捷使用它们,C# 用了一个特殊的机制,使得我们直接用nint和nuint就能搞定了,所以代码就变成了这样:

  本地函数是 C# 7 的一个概念,指的是某个方法只在这一个方法里才用得上,而且不破坏可读性的前提下(比如这个函数又比较短),我们就可以把这个函数嵌套声明到整个函数体内,起到一个看起来“内联”的效果。

  但是很遗憾,在 C# 7 里,本地函数是一个高级版的委托变量,它允许捕获变量,也允许传入 Lambda 的时候正常传递,这就是一个委托变量嘛!所以,既然是一个普通的变量,当然就不能标注特性了。

  我不知道你们是怎么在 C# 里处理函数指针的,我是用的委托。但是很遗憾的是,C# 一直没办法调用底层的函数指针的用法,要么就只能去写复杂的写法。

  这个函数里带有一个比较器函数指针comparer。但奈何我们没办法用函数指针,所以 C# 就只能先定义委托,然后传参了。

  这难受了,虽说委托写起来不是很复杂,但委托在最后也会被翻译成一个底层类。我们只是想让这个委托以函数指针的形式存在,但这么做一来是会产生额外的内存分配,二来是出现了很麻烦的调用机制(比如引用UnmanagedFunctionPointerAttribute来标记它本身其实是一个和底层函数指针交互的一个委托)。

  我们使用类似于委托的格式(用泛型参数列表专用的尖括号)把参数类型全部括起来就行了,最后一个是返回值类型。因为函数指针没办法像是Action和Func一样区分最后一个类型参数是不是返回值类型,所以函数指针即使返回类型是void也需要写上void。

  至于comparer和swapper传参的时候,你直接传函数名就行了。但需要注意的是,C 的函数指针在传函数进去的时候,&可省略;且调用方(*func)()的小括号和*也可以省略(即也可以写成func())。但 C# 的地址符号&不能省略,且调用的时候不写成(*func)()而是直接写作func(),这一点需要你注意。

  另外,函数指针和 C 的差不多,也可以转换为void*类型,但如果要从void*转回函数指针,需要强制转换。杏彩体育官网

  从 C# 7 开始,C# 开始支持模式匹配。模式匹配说白了就是基于数据的具体类型进行分情况讨论的一种用法。当一个类型需要从模糊转具体的时候,就需要模式匹配来判别具体的类型。C# 早期有is可以判断数据类型,有as可以进行尝试转换。但 C# 一直觉得这些写法还不够精简,所以发明了模式匹配的格式。

  C# 新增了三个关键字:and、or和not。它们专门用来检测和校验数据的具体类型,以及数值是否是正常结果。与此同时,也可以校验递归模式。

  其实也不难。b1是判断o是不是Person类型的。如果是,再看Age是不是 24。如果是,sunnie才被正常转换过来。换句线为true的时候,sunnie变量必定有值(不是未赋值状态),且Age属性肯定是 24 这个结果。

  not的推断了。我们可以认为,用大括号或者小括号一起判断的内容是且的关系,但对这样的条件取反就比较难理解了:

  or模式不能内联变量。因为判断的最终结果是不确定类型是什么的,如果我们用or校验数据类型的时候,就会出问题:

  static修饰 Lambda,来确保以后修改 API 和代码的时候,该 Lambda 不能捕获任何外部的数据。

  A、B、C和D四个属性,我们直接写A、B、C和D要写四遍属性,然后初始化的时候要给这个类型传四个参数的构造器,对应赋值到这四个属性上;而且如果我们要重写ToString和一些比较函数的操作的时候,还得自己写。

  R会展开,把小括号里的四个参数直接输出变为属性,R也会直接改成public sealed class R,里面包含了很多已经帮你写好了的方法:ToString、Deconstruct和Clone等等。

  Clone方法不能重写,它在记录类型里有特殊用途,这一点我们在之后的特性with表达式里讲。

  class的缘故,也有很多声音想要让团队去支持record继承自普通类的,或者普通类继承自record的,不过这一点我们只能耐心等待了。

  sealed、abstract等绝大多数修饰符,但static不行。因为static class用来提供静态操作的,但记录类型不是,它专门记录一个执行操作的效果,封装一个结果类型才出现的,所以没有static record。

  EqualityComparerT,它专门用来指定比较操作,其中有一个默认的比较属性叫Default,它给定了所有数据类型的默认比较行为(值类型是内存相等来比较,引用类型则是引用比较)。可问题就在于,值类型在实现相等性这一点比引用类型还复杂。值类型实现比较的时候,可能是递归比较的。比如有一个A的结构体,它里面都是内置的值类型(int、float这些),那挨个字段比较就完成了,对吧。但是如果嵌套了结构体的呢?是不是还得对嵌套的这个结构体内部再去实现比较器啊?

  with用来拓展一个记录,使得新的记录可以独立于原类型,并且在此之上更改和增加数据成员的数值:

  with关键字在后面跟上一个对象初始化器,以完成对新的数据的更新。当然,没有更新的数据就保持原本的数值,比如这里的q的Age还是 24。那么它是怎么实现的呢?

  Clone。这个方法为啥不可修改呢?因为这个方法是用来给with用的。换句话说,记录类型也是一个鸭子类型,只要你实现了Clone方法就能用with,因为内部会调用Clone方法后,才会修改掉那些具体的数值。如果 C# 让你修改Clone,整个执行操作的意义都变化了,以至于with的意义也发生了变化。C# 为了避免你这么做,就定了这么一个语法盐,不让你去动Clone。

  with表达式始终对于我们平时的操作都是很有意义的东西,但很遗憾的是,with只能用于记录类型,这让我们比较难过。比如,我在结构体或类里定义了一个复制构造函数public T(T older),或者实现Clone方法,应该with就可以用了。

  Clone的方法实现是不可控的,所以,复制构造函数的行为也是不可控的,所以你这样就会变更with具体的实现,因此 C# 不允许你这么干。这是很遗憾的。但是,我看到了 C# 团队在做完这个语法分析模块之后,就把开会讨论with的拓展这一内容提上了日程。我还是很欣慰的。

  init关键字了。init和get还有set这两个关键字被定义成完全平级的关键字,你可以当成一个特殊的set块,只是这个块在初始化就用得上一次以外就不可再修改了。比如说

  get所以不让用对象初始化器;但后者就不一样了,后者保证了对象可以在初始化的时候赋值一次,而这个“初始化”可以通过构造器本身,也可以对象初始化器。这一点来说,只有get的属性是不能用对象初始化器的。

  get; set;组合的属性,在底层被翻译成了一个字段、一个取值方法和一个赋值方法。它的代码是这样的:

  readonly的内部字段,那么set就可以改成init,因为readonly字段保证了字段只在初始化的时候提供修改和赋值,之后就一直保持不变。这不是就是完全类似于init的行为吗?

  readonly是只能在构造器里赋值;而init可以在初始化器里,也可以在构造器里。不知道你想过一个问题没有,有没有public readonly组合?有对吧,那么你可不可能从外界来为这个对象赋值呢?显然就不可能了。因为它们都是在类内部初始化的,要么在它旁边直接赋值,要么就是构造器了。

  init是为了配合记录类型才出现的一种语法,这也就意味着它在记录类型里就可以体现出非常神奇的语义。

  int到double是可以强制转换的,但由于两个值的类型不同,所以会出错。因此 C# 9 里判别类型是从左侧的d来看类型的,因此下面这个写法在 C# 9 里就不用对int.MaxValue再强制转换了。

  B这个派生类类型的,也可以是null,但总之跟A除了一个继承关系也就没啥别的关系了。我为了以后调用B这个GetNewOne方法的时候能够立即得到要么B类型实例要么null的线 就直接允许我们把返回值类型改成B?,以后就不必每次调用的时候还强制转换一下了。

  GetEnumerator方法,这个类型就可以用foreach了,但是我们知道,目前内置类型有些是无法foreach的,比如int。如果我要实现一个功能,来获取这个int类型数据的所有比特位为 1 的这个偏移量的话,就只能写一个比较丑的方法,然后去调用它了。C# 9 允许了迭代器拓展,我们只需要为这个类型实现GetEnumerator的扩展方法,也能应用到这个类型上了。

  static的,因此最好用静态类修饰),用一个静态方法完成初始化,你必须使用static关键字修饰这个方法,且带有特性ModuleInitializerAttribute。

  partial关键字修饰在方法上,强制将声明和实现分开成多个文件存放,以便体现出对文件的完整性和可读性,在 C# 3 里,分部方法都是隐式private的,所以你不能写任何访问修饰符;另外方法的返回值必须得是void的。部分程序员对于这一点来说,多少都有点难受和别扭,比如“凭啥返回值必须void啊,这完全不能理解”。

  void的话,就必须在另外一个文件给出实现了。换句话说,它起到了一个“类/结构体内部的接口约定”,接口是从外部约束的,你必须实现接口提供的所有纯抽象方法;分部方法则是内部约束,我们可以允许分部类分成多个文件,也可以允许你定义一些方法,但必须得有一个文件给出这个方法的具体实现,否则它会直接编译器错误。C# 3 的分部方法不需要实现,因为它是一种特殊的方法调用。如果你不实现它们,编译器会在编译的时候自动删除掉这些函数的调用,也就是说运行时你看不到它们了。

  半虚方法(或者一般就叫虚方法):方法本身已经提供了一种实现,你要做的就是要么不管(用默认的),要么就是去重写(override)它;

  纯虚方法:专门指的是抽象方法和接口里的不带访问修饰符的那些个方法。方法本身不实现就直接会报编译器错误;

  外部方法:方法不是 C# 来实现的,或者你使用的是别人写好的库函数,你只需要去调用它。这类方法用

  Main里去,所以你写的这些多的方法都是本地方法。所以说,你写的这些方法必须是不带访问修饰符的,最多只能有static关键字。

  T?的特殊用法。在 C# 8 里,由于泛型参数对值类型和引用类型的处理完全不同,这使得这个可空标记无法用于泛型参数上。在 C# 9 里,我们可以直接用在一个完全没有泛型约束的泛型参数上。如果它用在返回值上,就表示这个返回值可能为null,即类似于 C# 8 提供的特性MaybeNullAttribute;如果标记在参数上,则相当于AllowNullAttribute。

  where T : default约束。这个泛型约束用于override的方法上,且是在基类型的方法带有泛型参数时。这个泛型约束表达的是一种“泛型参数就是没有任何约束”的概念:

  record之后,编译器会直接看到record就可以认定它是记录了;如果没有record就是普通的类类型。这样对于编译器就会很方便。但如果没有这个关键字的话,编译器就需要读取后续的语法树的节点来判断到底是不是有小括号。C# 的编译器叫 Roslyn(这个单词就是用来表示一个女孩的名字,或者指的是一个城市名),她在解析一个代码的时候,会将其改变和转换为一个语法树的形式。也就是说,每一个部件(包括1 + 2的数字 1 和 2 之间的空格)都会分配到一个节点,它们通过具体的节点类型作区分然后构造出一棵树来,以便 Roslyn 的操作。

  is模式匹配,为了通用化处理,and/or/not模式连接的多个部件可以是比较表达式、类型名称、甚至是常量。在 Roslyn 处理的时候,只要我们发现是比较表达式、类型名、常量这些节点类型时,它们就能连接和拼接起来,所以这提高了代码分析的灵活性,也帮助了我们用户在写 C# 代码的时候,更加美观。

  record是考虑到 Roslyn 自己在作这个东西的分析的时候,更加方便,也为了以后拓展更多的数据类型,如果没有record,确实是可以区分,但一来是降低了分析效率,二来是加大了分析难度,三来是不便于拓展。万一(我是说万一)将来 C# 编译器又允许在类类型后加中括号来表示一些其他的玩意儿了呢(笑