C# 12
前言
C# 12 是 .NET 8 版本推出的,目前还在 RC 阶段。预计 2023 年 11 月份就会正式推出了。
想提早玩玩的话可以使用用 Visual Studio Preview。
参考
Primary Constructors
参考:
Docs – Tutorial: Explore primary constructors
YouTube – The New Constructor Type Coming in C# 12 is Weird
C# 9.0 推出的 Record 就有 Primary constructors 的概念。
它的 definition 非常短
public record Dimension(int Width, int Height);
相同的逻辑,class 就很长很繁琐。
public class Dimension { public int Width { get; set; } public int Height { get; set; } public Dimension(int width, int height) { Width = width; Height = height; } }
于是,C# 12 也让 class 和 struct 有了 primary constructors 的功能。
但是要注意哦,它们有一些些微妙的不同,很容易让你掉坑。
Class primary constructors != Record primary constructors
public class Dimension(int width, int height);
上面这句和 Record 语法类似,但是效果却差很多。
首先,它的参数是 camelCase,Record 是 PascalCase,为什么呢?
因为 Record 的参数是 public property,而 class 的 parameter 是 private field。
所以,property 的写法应该是长这样的。
public class Dimension(int width, int height) { public int Width { get; set; } = width; public int Height { get; set; } = height; }
不管怎样,它还是比之前短了一些。还是不错用的。
Parameters can be use anywhere
参数不仅仅可以用于 assign to property,它也可以在方法中使用。
public class Dimension(int width, int height) { public int Sum() { return width + height; } }
原因上面提到了,paramters 其实是 private filed 来的
我们把代码放到 sharplab.io 查看就明白了
简单说,primary constructors 就是语法糖,它就是替你写了一个 constructor 还有 private fields.
既然是 private field,那是不是应该加 prefix underscore 呢?
er...这有一点两面,作为 privite field 应该要有 underscore,但作为参数却没用。
那它算是 field 还是参数呢?
依照微软的例子,是把它当参数看多一点。
Dependency Injection
C# 11
public class Dimension { private readonly Service _service; public Dimension(Service service) { _service = service; } }
C# 12
public class Dimension(Service service) { private readonly Service _service = service; }
如果你不是很在意 readonly 的话,可以省略
public class Dimension(Service service) { }
本来听说会支持 readonly 语法
public class Dimension(readonly Service service) { }
但最后视乎是被否决掉了。
总结
C# 12 以后,Record, Class, Struct 又又又更加像了,更傻傻分不清楚了,更容易掉坑了。
记住,class 的 primary constructors 是 constructor + private fields, Record 则是 constructor + public properties。
Collection Expressions
日常我们定义 collection 的方式
var numbers = new List<int> { 1, 2, 3 };
上面是 var 推导类型的 defined 写法
换成先 declare 类型是这样
List<int> numbers = new() { 1, 2, 3 };
C# 12 对上面这句语法又做了优化,现在只需要这样写
List<int> numbers = [1, 2, 3];
单纯比长度的话,本来只是少了 var 现在又少了 new 确实短了不少。var 剩下的唯一好处就只有起头字数一样,可以对齐。
它只是语法糖而已,通过 sharplab.io 查看
转译出来就是 new List + add item
Spread Operator
写过 JavaScript 的人对 spread operator 应该不陌生了。C# 12 也有了这个功能。
var numbers = new List<int> { 1, 2, 3 }; List<int> numbers2 = [..numbers, 4, 5]; // 语法正确 var number3 = new List<int> { ..numbers2, 6, 7 }; // 语法错误哦
Default lambda parameters
C# 11 lambda 是不支持 default parameter value 的。C# 12 以后支持了。
C# 12
如何动态创建表达式树 for default parameters 我还没有研究。以后补上。
Alias any type
Alias type 有点像 TypeScript 的 Type Aliases,但也不完全一样。
它的写法长这样
using Point = Coordinate; public class Coordinate { public int X { get; set; } public int Y { get; set; } }
它可以完全用一个 alias 别名取代任何类型,就真的是 rename 而已哦。
var point = new Point();
上面这个在 C# 是 ok 的,但在 TypeScript 是做不到的。
使用场景我还没有什么体会,目前看到的第一个好处是可以用来 define Tuple。
using Person = (string Name, int Age); public class Service { public void DoSomething(Person[] people) { var service = new Service(); service.DoSomething([("Derrick", 1)]); } }
这个应该是最短的写法了。从前至少都要使用 Record,而 Record 是需要 new () 的,上面连 new 都不需要了。
局限
目前看到 2 个局限
1. using 的位置只能在最顶部,这个不够方面。
2. alias 不可用于 alias
using Person = (string Name, int Age); using People1 = Person[]; // Error: The type or namespace name 'Person' could not be found using People2 = List<Person>; // Error: The type or namespace name 'Person' could not be found
上面 2 个写法都不支持。