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(); |