萌ICP备20220458号

C# 10 速览

2021年11月21日 3121点热度 1人点赞 0条评论

.NET 6 和 Visual Studio 2022 已于11月8日正式发布。内容很多,有兴趣可以自行了解,本文主要简单介绍一下 C# 10 的新功能。

本来我是把 DevBlog 全文翻译了的,但数据库丢了(悲),所以写个速览补上。

概览

  • 全局 using
  • 隐式 using
  • 文件范围的命名空间
  • lambda 和方法组的自然类型
  • lambda 的返回类型
  • lambda 上的特性
  • 结构的无参构造函数
  • 结构记录
  • 记录的密封 ToString()
  • 用于结构和匿名类型的 with 表达式
  • 内插字符串处理程序
  • 常量内插字符串
  • 解构时混合声明与赋值
  • 改进的赋值明确性推断
  • 扩展的属性模式
  • 调用方表达式
  • 还在预览的最重要的功能

using 指令

using,每个文件都有,内容还大同小异,不如简化一下:

全局 using

在任何一个合法的 using 指令之前,加上一个 global 关键字,就可以进行 global using。它的作用就是让这条 using 对整个项目生效。

注意,是任何一个。using A = B;using static 也是可以的,把 using 放在哪个文件也没区别。所以 C# 也能实现全局函数了?

隐式 using

不同的项目类型会自带一些不同的全局 using,可以在项目文件里启用。因为不用你手写,所以叫隐式 using。

但实际上你想写也是可以写的,项目文件里加一行就行。同理,想删也一样。

文件范围的命名空间

namespace 现在可以单独拿出来用了。写上以后,效果就是为所在文件中的所有类型指定命名空间。

基本就是可以少一个缩进。

lambda 表达式和方法组

C# 最近几个版本都加了一些方便 FP 的功能,这次也不例外:

lambda 的自然类型

以前如果使用 lambda,必须有一个明确的目标委托类型。所以像 var 或者把 lambda 当成 object 之类的场景就显得很啰嗦。C# 10 开始,如果能推断出来,lambda 会有一个默认的类型。这个默认类型会尽量是 Action<...> 或者 Func<...>;如果不行,编译器会生成一个匿名委托类型。表达式树也是支持的。

方法组的自然类型

只有一个重载的方法组和 lambda 有什么区别呢?所以它们也有自然类型,和 lambda 类似。

lambda 的返回类型

为了减少没法推断类型的情况,现在 lambda 可以指定返回类型。把返回类型放在 lambda 的参数列表之前即可。为了避免迷惑,参数列表的括号不能省略。

lambda 上的特性

lambda 编译出来就是方法,当然可以有特性。以及现在静态代码分析也很依赖特性,所以 C# 10 允许在 lambda 上加特性了。语法就是在 lambda 前面加中括号。

结构

结构一直以来功能都比类少很多,导致很多时候没法利用它提高性能。C# 10 希望可以减轻一下这种情况:

无参构造函数和字段初始化器

C# 10 之前结构的无参构造函数是自动生成的,而且如果你尝试自己写一个就会报错。现在就不会了,你不仅可以写无参构造函数,还可以给字段赋默认值。

但是吧,性能不可能丢,CLR 更不会改。只有用 new 创建的结构才会调用你的构造函数;用 default 或者数组里创建的还是会给所有字段赋默认值。

记录结构

原来的记录只能是类,现在可以是结构了。记录结构用 record struct 声明,原来的记录类现在也可以用 record class 声明。

记录结构会自动实现 IEquatable<T>== 运算符,替换掉自带的反射实现。记录的 ToString() 重写也是有的。

像记录类一样,记录结构也可以用位置参数定义。语法我没发现什么区别。

但是!自动生成的属性是可变的,因为这样可以更方便地替换匿名元组类型。给记录结构或者某个参数加上 readonly 可以让它不可变。

记录类中密封的 ToString()

现在可以给记录类的 ToString() 方法加上 sealed,阻止编译器为派生记录生成重写。

用于结构和匿名类型的 with 表达式

就是标题说的这个东西现在有了。

内插字符串

C# 团队说当时加这个功能的时候就觉得不够好,然后现在终于能改了?

内插字符串处理程序

目前,内插字符串都会被编译成 string.Format,不仅造成性能问题,还限制了各种花里胡哨的操作。C# 10 为库提供了一种模式,可以“接管”对内插字符串的处理。

比如,StringBuilder 就新增了一个 Append(ref StringBuilder.AppendInterpolatedStringHandler handler) 重载,在传入内插字符串时优先级更高,可以提高性能。一般来说,看到和这个长得差不多的 API,就是在用这个模式了。

另外两个例子:Debug.Assert 在条件为真时不会计算后面的内插字符串;String.Create() 可以为内插字符串里面的参数指定 IFormatProvider

常量内插字符串

如果是把常量字符串内插进字符串,现在生成的字符串也可以当常量用了。但如果内插的是个数字常量就不行,因为不同地区数字显示方式不同,只能运行时计算。

其他改进

混合解构

以前,解构之后要不然都赋值给已有变量,要不然都声明为新的。现在可以混合这两者,既有已有的又有新的。不知道为什么,这让我想到了 Go 的错误处理(。

明确赋值

C# 中,可以先声明变量再赋值。但在使用值之前必须确保变量有值,不管中间是有分支、循环还是其他什么结构。

编译器如果分析出变量有可能在使用时没有值,就会报错。C# 10 降低了误报率。

扩展的属性模式

现在,在模式匹配中,嵌套的属性模式可以直接通过用一个 . 连接多个属性名称代替,简化代码,方便阅读。

调用方表达式

和之前已有的调用方信息特性一个原理,新增了一个 CallerArgumentExpressionAttribute,可以获取调用方在某个参数位置填了什么表达式,便于调试和抛异常。

结语

最重磅的功能,接口的静态抽象成员,估计是赶不上发布了,需要再预览一段时间,那我也就不介绍了,正式出来再说吧。

我觉得其实现在不用着急,等都预览完了再更也不迟。

本文主要还是按照这篇 DevBlog 写的,里面更详细,而且有很多链接,想深入了解可以看一看。

funnysyc

啥都不会。

文章评论