这节回顾一下C# 8.0中的新增特性。
Readonly成员:
C#8.0中,可将readonly修饰符应用于结构的成员,它指示该成员不会修改状态。这比将readonly修饰符直接用于struct声明更加精准。
有如下代码:
public struct Point
{
publicdouble X { get; set; }
publicdouble Y { get; set; }
publicdouble Distance => Math.Sqrt(X * X + Y * Y);
public override string ToString() =>
$"({X}, {Y}) is {Distance} from the origin";
}
与大多数结构一样,ToString() 方法不会修改状态。 可以通过将 readonly 修饰符添加到 ToString() 的声明来对此进行指示 :
public readonly override string ToString() =>
$"({X}, {Y}) is {Distance} from the origin";
此时编译器会报错,因为tostring()访问了未标记为readonly的属性:Distance,因此可以通过将 因此可以通过将 readonly 修饰符添加到声明来修复此警告:
public readonly double Distance => Math.Sqrt(X * X + Y * Y);
readonly
修饰符对于只读属性是必需的。 readonly 修饰符对于只读属性是必需的。 编译器会假设 get 访问器可以修改状态;必须显式声明 readonly。 自动实现的属性是一个例外;编译器会将所有自动实现的 Getter 视为 readonly,因此,此处无需向 X 和 Y 属性添加 readonly 修饰符。
默认接口方法:
C#8.0可以将成员添加到接口,并为这些成员提供实现。 借助此语言功能,API 作者可以将方法添加到以后版本的接口中,而不会破坏与该接口当前实现的源或二进制文件兼容性。 现有的实现继承默认实现 。 此功能使 C# 与面向 Android 或 Swift 的 API 进行互操作,此类 API 支持类似功能。 默认接口方法还支持类似于“特征”语言功能的方案。
升级的switch表达式:
switch表达式:借助switch表达式,可以使你的代码更加简洁优雅:
请看如下代码:
publicenum Rainbow
{
Red,
Orange,
Yellow,
Green,
Blue,
Indigo,
Violet
}
通过枚举获取颜色RGB值
public static RGBColor FromRainbow(Rainbow colorBand) =>
colorBand switch
{
Rainbow.Red => new RGBColor(0xFF, 0x00, 0x00),
Rainbow.Orange => new RGBColor(0xFF, 0x7F, 0x00),
Rainbow.Yellow => new RGBColor(0xFF, 0xFF, 0x00),
Rainbow.Green => new RGBColor(0x00, 0xFF, 0x00),
Rainbow.Blue => new RGBColor(0x00, 0x00, 0xFF),
Rainbow.Indigo => new RGBColor(0x4B, 0x00, 0x82),
Rainbow.Violet => new RGBColor(0x94, 0x00, 0xD3),
_ => thrownew ArgumentException(message: "invalid enum value", paramName: nameof(colorBand)),
};
此处与以往的switch语句有如下几点改进:
1.变量位于 switch 关键字之前
2.将 case
和 :
元素替换为 =>
(此处借鉴了lambda表达式的语法格式)
3.将 default
事例替换为 _
弃元
4.正文是表达式,不是语句
//与经典switch代码做比较
public static RGBColor FromRainbowClassic(Rainbow colorBand)
{
switch (colorBand)
{
case Rainbow.Red:
return new RGBColor(0xFF, 0x00, 0x00);
case Rainbow.Orange:
return new RGBColor(0xFF, 0x7F, 0x00);
case Rainbow.Yellow:
return new RGBColor(0xFF, 0xFF, 0x00);
case Rainbow.Green:
return new RGBColor(0x00, 0xFF, 0x00);
case Rainbow.Blue:
return new RGBColor(0x00, 0x00, 0xFF);
case Rainbow.Indigo:
return new RGBColor(0x4B, 0x00, 0x82);
case Rainbow.Violet:
return new RGBColor(0x94, 0x00, 0xD3);
default:
throw new ArgumentException(message: "invalid enum value", paramName: nameof(colorBand));
};
}
属性模式:
借助属性模式 ,可以匹配所检查的对象的属性。 请看一个电子商务网站的示例,该网站必须根据买家地址计算销售税。 这种计算不是 Address
类的核心职责。 它会随时间变化,可能比地址格式的更改更频繁。 销售税的金额取决于地址的 State
属性。 下面的方法使用属性模式从地址和价格计算销售税:
public static decimal ComputeSalesTax(Address location, decimal salePrice) =>
location switch
{
{ State: "WA" } => salePrice * 0.06M,
{ State: "MN" } => salePrice * 0.075M,
{ State: "MI" } => salePrice * 0.05M,
//...
_ => 0M
};
元组模式:
一些算法依赖于多个输入。 使用元组模式,可根据表示为元组的多个值进行切换 。 以下代码显示了游戏“rock, paper, scissors(石头剪刀布)”的切换表达式:
public static string RockPaperScissors(string first, string second)
=> (first, second) switch
{
("rock", "paper") => "rock is covered by paper. Paper wins.",
("rock", "scissors") => "rock breaks scissors. Rock wins.",
("paper", "rock") => "paper covers rock. Paper wins.",
("paper", "scissors") => "paper is cut by scissors. Scissors wins.",
("scissors", "rock") => "scissors is broken by rock. Rock wins.",
("scissors", "paper") => "scissors cuts paper. Scissors wins.",
(_, _) => "tie"
};
Using声明:
using 声明 是前面带 using 关键字的变量声明。 它指示编译器声明的变量应在封闭范围的末尾(也就是执行到using的闭合花括号处)进行处理,对using引用的对象,要继承于IDisposable接口,因为在using块执行完毕后会自动调用该实例对象的Dispose()方法,将其释放,这也是为什么使用using的原因,它将代码简化了。
C#8.0中,使用using可以不再将其用括号括起来,而是像一个关键字一样,在加在对象声明语句的开头,请看如下代码:
当运行到最后一个大括号时就会自动释放file对象。
静态本地函数:
现在可以向本地函数添加 static
修饰符,以确保本地函数不会从封闭范围捕获(引用)任何变量。下面的代码包含一个静态本地函数。 它可以是静态的,因为它不访问封闭范围中的任何变量:
可处置的ref结构:
用ref修饰符声明的 struct 可能无法实现任何接口,因此无法实现 IDisposable。要能够处理 ref struct,它必须有一个可访问的 void Dispose() 方法。 此功能同样适用于 readonly ref struct 声明。
在可为空注释上下文中,引用类型的任何变量都被视为不可为空引用类型 。 若要指示一个变量可能为 null,必须在类型名称后面附加 ?,以将该变量声明为可为空引用类型 。
对于不可为空引用类型,编译器使用流分析来确保在声明时将本地变量初始化为非 Null 值。 字段必须在构造过程中初始化。 如果没有通过调用任何可用的构造函数或通过初始化表达式来设置变量,编译器将生成警告。 此外,不能向不可为空引用类型分配一个可以为 Null 的值。
不对可为空引用类型进行检查以确保它们没有被赋予 Null 值或初始化为 Null。 不过,编译器使用流分析来确保可为空引用类型的任何变量在被访问或分配给不可为空引用类型之前,都会对其 Null 性进行检查。
异步流:
从 C# 8.0 开始,可以创建并以异步方式使用流。 返回异步流的方法有三个属性:
使用异步流需要在枚举流元素时在 foreach 关键字前面添加 await 关键字。 添加 await 关键字需要枚举异步流的方法,以使用 async 修饰符进行声明并返回 async 方法允许的类型。 通常这意味着返回 Task 或 Task<TResult>。 也可以为 ValueTask 或 ValueTask<TResult>。 方法既可以使用异步流,也可以生成异步流,这意味着它将返回 IAsyncEnumerable<T>。
请看如下代码:
使用await foreach 语句来枚举序列:
可以在创建和使用异步流的教程中自行尝试异步流。
默认情况下,在捕获的上下文中处理流元素。 如果要禁用上下文捕获,请使用 TaskAsyncEnumerableExtensions.ConfigureAwait 扩展方法。
从 C# 8.0 开始,语言支持实现 System.IAsyncDisposable 接口的异步可释放类型。using 表达式的操作数可以实现 IDisposable 或 IAsyncDisposable。 如果继承 IAsyncDisposable,编译器将生成代码, await 从 IAsyncDisposable.DisposeAsync 返回的 Task。
索引和范围:
索引和范围为访问序列中的单个元素或者一段范围提供了简洁的语法。此语言支持依赖于两个新类型和两个新运算符:System.Index 表示一个序列索引来自末尾运算符 ^ 的索引,指定一个索引与序列末尾相关System.Range 表示序列的子范围,范围运算符 ..,用于指定范围的开始和末尾,就像操作数一样
让我们从索引规则开始:数组 sequence.0 索引与 sequence[0] 相同,^0 索引与 sequence[sequence.Length] 相同。sequence[^0] 不会引发异常,就像 sequence[sequence.Length] 一样。 对于任何数字 n,索引 ^n 与 sequence.Length - n 相同。范围指定范围的开始和末尾 。 包括此范围的开始,但不包括此范围的末尾"[ )",范围 [0..^0] 表示整个范围,就像 [0..sequence.Length] 表示整个范围。
请看如下代码:
可以使用 ^1 索引检索最后一个词:
words[1..4] :包含word[0],word[1],word[2],word[3],但不包括word[4]
以下是一些特殊用法:
words[..]; // contains "The" through "dog"
words[..4]; // contains "The" through "fox"
words[6..]; // contains "the", "lazy" and "dog"
传入范围变量也是可以的:
Range phrase = 1..4;
var text = words[phrase];
Null 合并赋值:
C# 8.0 引入了 null 合并赋值运算符 ??=
。 仅当左操作数计算为 null
时,会将其右操作数的值分配给左操作数。
在 C# 7.3 及更低版本中,构造类型(包含至少一个类型参数的类型)不能为非托管类型。 从 C# 8.0 开始,如果构造的值类型仅包含非托管类型的字段,则该类型不受管理。
例如,假设泛型 Coords<T> 类型有以下定义:
Coords<int> 类型为 C# 8.0 及更高版本中的非托管类型。 与任何非托管类型一样,可以创建指向此类型的变量的指针,或针对此类型的实例在堆栈上分配内存块:
从C# 8.0开始,如果 stackalloc 表达式的结果为 System.Span<T> 或 System.ReadOnlySpan<T> 类型,则可以在其他表达式中使用stackalloc表达式:
内插逐字字符串中 $ 和 @ 标记的顺序可以任意选择:$@"..."和@$"..."安排当前有效的内插字字符串。在早期的C#版本中,$必须出现在@标记之前。
本节到此结束...
要参考更多信息,请查阅官方文档:
https://docs.microsoft.com/zh-cn/dotnet/csharp/whats-new/csharp-8#default-interface-methods