C# 各版本新特性
仅记录 C# 5 及以后版本
C# 5
随 Visual Studio 2012 发布
异步 async/await
| 1 | async Task Func() { | 
调用方信息特征
可以通过特性,在函数参数,取得调用方信息
- CallerMemberName 调用者函数名/属性名等
- CallerFilePath 调用者文件地址
- CallerLineNumber 调用者代码行数
C# 6
随 Visual Studio 2015 发布
using 静态引入
可以 using 引入类的静态成员
| 1 | using static namespace.Class; | 
引入后可直接访问该类中的静态成员
using 引入别名
可以给引入的命名空间取别名
| 1 | using name = namespace.subnamespace; | 
异常筛选器
支持异常的条件过滤
| 1 | try | 
自动属性初始化表达式
为属性赋初始值
| 1 | public static int Id { get; set; } = 123; | 
Expression-bodied 成员
用 Lambda 表达式定义类成员
只读属性
| 1 | public static DateTime Time => DateTime.Now; | 
get/set 属性
| 1 | public string Name | 
无返回值方法
| 1 | public void Func(int id) => this.Id = id; | 
有返回值方法
| 1 | public int Func(int id) => id + 1; | 
构造函数
| 1 | public class Location | 
null 传播器
运算符 ?. 和 ?[]
| 1 | var title = user?.todos?[0]?.title; | 
字符串插槽
用 $ 标识的字符串,其中 {} 内可写变量或表达式
| 1 | var name = "hal.wang"; | 
nameof 运算符
获取变量的名称
| 1 | var user1 = new User(); | 
C# 7
随 Visual Studio 2017 发布
out 变量
简化之前的 out 变量写法,可以不用单独定义变量
以前的写法为
| 1 | var intput = "123"; | 
现在的写法可以是
| 1 | var intput = "123"; | 
元组
升级 Tuple 写法
| 1 | (double, int) t1 = (4.5, 3); | 
| 1 | (double Sum, int Count) t2 = (4.5, 3); | 
| 1 | var t3 = (Sum: 4.5, Count:3); | 
类型相同可赋值
| 1 | (double, int) t1 = (4.5, 3); | 
判断相等
| 1 | (int a, byte b) left = (5, 10); | 
模式匹配
用 is 和 is not 判断变量类型,也可以判断 null
| 1 | var i = 1; | 
比较离散值(switch)
| 1 | public string Func(string command) => command switch | 
关系模式(switch)
| 1 | public string Func(int num) => num switch | 
本地函数
函数内部定义函数
弃元
用 _ 命名的变量,可重复
命名参数
默认参数可指定参数名传参
| 1 | public void Func(int arg1 = null, int arg2 = null) | 
ref
将值类型声明为引用类型
| 1 | int number = 6; | 
C# 8
是专门面向 .net core 的第一个主要 C# 版本
默认接口方法
可以不止是约束,也可以实现完整的方法
模式匹配
增强 switch 的模式匹配
属性模式
可以匹配对象中的属性
| 1 | public class Cls | 
元组模式
可以匹配元组
位置模式
按属性位置取,组成元组,然后匹配
using 新版声明
using 块如果范围为整个函数,可以省略大括号
静态本地函数
本地函数可以声明为静态的
可处置的 ref 结构
可以用 ref 声明 struct,表示此 struct 的实例对象都是引用类型
可空引用类型
更灵活的可空特性
通过 ? 为字段、属性、方法参数、返回值等添加是否可为 null 的特性
异步流
可以返回异步版本的迭代器
| 1 | async IAsyncEnumerable<int> GetList() | 
异步释放
增加 IDisposable 的异步版本 IDisposableAsync
接口函数为 DisposeAsync()
| 1 | public class Cls : IAsyncDisposable | 
索引和范围
可以指定数组范围
| 1 | var nums = new int[]{ | 
声明范围
| 1 | int[] nums = [1, 2, 3, 4, 5, 6, 7, 8, 9]; | 
null 合并赋值
语法 ??=
如果左侧不为空,直接返回左侧
如果左侧为空,则将右侧赋值给左侧,然后返回左侧
| 1 | // WPF 常用 | 
内插字符串增强功能
可以结合 $ 和 @ 声明字符串,结合二者功能,声明顺序不分先后
| 1 | var text = $@"abc\de{12}"; | 
C# 9
随 .NET5 一起发布,是面向 .NET5 版本的任何程序集的默认语言版本
记录类型
用 record 声明的类型
是新的引用类型,相当于引用类型的 struct
与类的区别是,record 可以使用基于值的相等性
简单声明
可以简单声明只读记录
| 1 | record Personal(string FirstName, string LastName); | 
可初始化记录
也可以用 init 声明可初始化的记录,只能在构造函数中或初始化类时赋值
| 1 | public record Personal | 
读写记录
也可以声明可读写记录
| 1 | public record Personal | 
值相等性
同类 record 的两个实例对象,只要属性值都相同,== 运算符就为 true
运行时类型必须相等,派生类型也不行
非破坏性修改
用 with 基于已有记录,新建一条记录
| 1 | Personal person1 = new("n1", "n2"); | 
ToString
.ToString 会格式化属性名和属性值
| 1 | DailyTemperature { HighTemp = 57, LowTemp = 30, Mean = 43.5 } | 
仅限 init 的资源库
用 init 替换 set 声明属性,只能在构造函数或初始化时设置属性值
顶级语句
不用 Main 方法和 namespace,直接写函数体代码
模式匹配增强功能
改进模式匹配
- 类型模式匹配一个与特定类型匹配的对象
- 带圆括号的模式强制或强调模式组合的优先级
- 联合 and模式要求两个模式都匹配
- 析取 or模式要求任一模式匹配
- 否定 not模式要求模式不匹配
- 关系模式要求输入小于、大于、小于等于或大于等于给定常数。
| 1 | public static bool IsLetter(this char c) => c is >= 'a' and <= 'z' or >= 'A' and <= 'Z'; | 
| 1 | public static bool IsLetterOrSeparator(this char c) => c is (>= 'a' and <= 'z') or (>= 'A' and <= 'Z') or '.' or ','; | 
可省略 new
已知类型 new 表达式中可以省略类型
| 1 | private List<WeatherObservation> _observations = new(); | 
静态匿名函数
允许对 lambda 和匿名方法,使用 static 修饰符
| 1 | Action act = static () => { | 
扩展 GetEnumerator
允许 foreach 循环识别扩展方法 GetEnumerator
| 1 | public class PeopleExtensions{ | 
类内部需要实现迭代器功能,即包含 Current 和 MoveNext
lambda 弃元参数
可以用 _ 作为 lambda 和匿名方法的参数
- lambda: (_, _) => 0,(int _, int _) => 0
- 匿名方法: delegate(int _, int _) { return 0; }
分部方法
对于 partial 类,可以在一处声明,在另一处实现
| 1 | partial class D | 
C# 10
.NET 6 的默认语言版本为 C# 10
记录结构
扩展之前的 record 记录
- 用 record struct声明值类型
- 用 record class声明引用类型
- 用 readonly record struct声明只读值类型
无参构造函数
| 1 | record struct Person | 
有参构造函数
| 1 | record struct Person(string name) | 
全局 using
一次引入,整个项目可用
| 1 | global using System.Math; | 
文件范围命名空间
用新方式声明 namespace 后,整个文件中的类都是属于这个命名空间
可以减少一组大括号,增加易读性
| 1 | namespace CustomNamespace; | 
扩展属性模式
模式匹配可以用嵌套的属性或字段
| 1 | object people = new People | 
lambda 改进
- Lambda 表达式可以具有自然类型,这使编译器可从 Lambda 表达式或方法组推断委托类型。
- 如果编译器无法推断返回类型,Lambda 表达式可以声明该类型。
- 特性可应用于 Lambda 表达式。
- Lambda 支持关键字修饰,如 ref/out 等
特性应用到函数
| 1 | var fn1 = [CustomAttribute] () => { }; | 
特性应用到参数
| 1 | var concat = ([DisallowNull] string a, [DisallowNull] string b) => a + b; | 
特性应用到返回值
| 1 | var inc = [return: NotNullifNotNull(nameof(s))] (int? s) => s.HasValue ? s++ : null; | 
常量内插字符串
如果内插变量都是 const 修饰的,那么内插字符串也可以用 const 修饰
| 1 | const string str1 = "s1"; | 
记录类型可密封 ToString
用 sealed 修饰 ToString,子类不能再覆写 ToString
C# 11
.NET 7 的默认语言版本为 C# 11
泛型特性
可创建泛型类并继承 Attribute,实现泛型特性
| 1 | public class GenericAttribute<T> : Attribute { } | 
| 1 | [] | 
泛型类型必须完全构造,不能有任何参数
- 不能是 dynamic,可以是 object
- 不能是 string?/int?等可空引用类型,应使用 string/int 等
- 不能用元组如 (int X, int Y),可使用ValueTuple<int, int>
| 1 | public class GenericType<T> | 
字符串内插中允许换行
在字符串内插中 { }, 允许换行
| 1 | var str = $"abc{ | 
列表的模式匹配
可以将数组或列表与模式的序列进行匹配
| 1 | int[] numbers = { 1, 2, 3 }; | 
弃元
弃元可以匹配任何值
| 1 | int[] numbers = { 1, 2, 3 }; | 
取值
| 1 | List<int> numbers = new() { 1, 2, 3 }; | 
切片
在模式匹配中可使用切片模式
切片可匹配 0 个或多个元素,最多出现一个切片
| 1 | Console.WriteLine(new[] { 1, 2, 3, 4, 5 } is [> 0, > 0, ..]); // True | 
原始字符串文本
用 3 个双引号 """ 开头和结尾
| 1 | string longMessage = """ | 
UTF-8 字符串字面量
用 u8 后缀修饰字符串字面量,可指定 UTF-8 编码
| 1 | ReadOnlySpan<byte> AuthWithTrailingSpace = new byte[] { 0x41, 0x55, 0x54, 0x48, 0x20 }; | 
必须的成员
用 required 修饰属性,要求必须初始化该属性
| 1 | public class Person | 
如果用构造函数初始化,需要用 SetsRequiredMembers 特性修饰构造函数
| 1 | public class Person | 
文件本地类型
用 file 修饰类型,限制该类型的可见性为文件内
| 1 | file class HiddenWidget | 
在其他代码文件的任何命名空间中,即使在部分类中,都无法访问该类型
C# 12
.NET 8 的默认语言版本为 C# 12
主构造函数
不再局限于 record 类型,也可以在 class 和 struct 中使用主构造函数
| 1 | public class ExampleController(IService service) : ControllerBase | 
集合的展开运算符
可以使用展开运算符 .. 将集合中的元素与其他集合内敛
| 1 | int[] arr1 = [1, 2, 3]; | 
类似 js 中对数组操作的展开运算符 ...
默认 Lambda 参数
可以为 Lambda 表达式的参数定义默认值,与普通方法相同
类型别名
可以用 using 指令为任意类型创建别名,类似 ts 中的 type
| 1 | using CustomType = (int X, int Y); | 
| 1 | using CustomList = System.Collections.Generic.List<int>; | 
内联数组
内联数组是包含多个元素的连续块的结构,适用于高性能场景
- 内联数组是 struct
- struct 仅有 1 个字段
- struct 未指定显式布局
- 使用 System.Runtime.CompilerServices.InlineArrayAttribute特性修饰 struct
- System.Runtime.CompilerServices.InlineArrayAttribute参数必须大于 0
| 1 | [] | 
| 1 | var buffer = new Buffer(); |