看新闻很累? 看技术新闻更累? 试试 下载 InfoQ 手机客户端 https://time.geekbang.org/?utm_source=website&utm_medium=infoq&utm_campaign=news&utm_content=app , 每天上下班路上听新闻, 有趣还有料!
在 C# 的未来特性清单上, 排在第一位的是可空引用类型. 我们第一次报道这个特性是在去年, 这里我们简要的回顾一下: 所有的引用变量, 参数和字段默认都是非空的. 然后, 和值类型一样, 如果你希望它们可以为空, 你就必须在类型名上加一个问号 (?) 来显式说明.
这会是一项可选特性, 目前的想法是, 对于将升级到 C# 8 的现有项目, 可空引用类型特性是关闭的. 对于新项目, 微软倾向于默认开启这项特性.
警告会进一步分成潜在错误和表面警告. 例如, 如果 p.MiddleName 是一个 string?, 那么下面这行代码会是一个表面警告:
string middleName = p.MiddleName;
由于危险只会出现在值解引用的时候, 所以这种对局部变量的赋值并不是一个真正的问题. 因此, 你可以在遗留代码上禁用这个警告, 以减少误报数量.
同样, 早于这项特性的库也不会触发警告, 因为编译器不知道一个指定的参数是否应该视为可空的.
GitHub 上提供了 可空引用类型的预览 https://github.com/dotnet/csharplang/wiki/Nullable-Reference-Types-Preview .
Switch 表达式
Switch 块通常用于简单地返回单个值. 在这个常见的场景中, 其语法比实际完成的工作要复杂得多. 考虑下下面这个使用模式匹配的例子:
- static string M(Person person)
- {
- switch (person)
- {
- case Professor p:
- return $"Dr. {p.LastName}";
- case Studen s:
- return $"{s.FirstName} {s.LastName} ({s.Level})";
- default:
- return $"{person.FirstName} {person.LastName}";
- }
- }
在新的提案中, 反复出现的 case 和 return 语句可以省掉, 其结果是下面这种更新, 更紧凑的语法:
- static string M(Person person)
- {
- return person switch
- {
- Professor p => $"Dr. {p.LastName}",
- Student s => $"{s.FirstName} {s.LastName} ({s.Level})",
- _ => $"{person.FirstName} {person.LastName}"
- };
- }
具体的语法还在讨论之中. 例如, 现在还不确定这个特性是要使用 =>还是: 分割模式表达式和返回值.
Property 模式匹配
当前, 可以通过 when 子句执行 Property 层的模式匹配.
case Person p when p.LastName == "Cambell" : return $"{p.FirstName} is not enrolled";
借助 "Property 模式", 上述代码就简化为:
case Person { LastName: "Cambell"} p : return $"{p.FirstName} is not enrolled";
变化很小, 但却让代码更清晰, 也去掉了一些冗余的变量名称.
从模式中提取 Property
更进一步, 你可以在模式中声明变量:
case Person { LastName: "Cambell", FirstName: var fn} p : return $"{fn} is not enrolled";
模式中的 "解构器(Deconstructor)"
解构器用于把对象解构成其组成部分. 它主要用于从元组多重赋值. 在 C# 7.3 中, 你还可以把解构器和模式匹配一起使用.
在下面的例子中, Person 类被解构成{FirstName, MiddleName, LastName}. 由于我们不使用 MiddleName, 所以我们使用一条下划线来跳过这个属性:
case Person ( var fn, _, "Cambell" ) p : return $"{fn} is not enrolled";
递归模式
下一个模式, 假设 Student 类解构成{FirstName, MiddleName, LastName, Professor}. 我们可以同时解构 Student 对象及其子类 Professor 对象.
case Student ( var fn, _, "Cambell", var (_, _, ln) ) p : return $"{fn} is enrolled in {ln}'s class";
在这行代码中, 我们:
把 student.FirstName 提取到 fn
跳过 student.MiddleName
把 student.LastName 匹配成 "Cambell"
跳过 student.Professor.FirstName 和 student.Professor.MiddleName
把 student.Professor.LastName 提取到 ln
GitHub 上提供了 递归模式匹配的预览 https://github.com/dotnet/csharplang/wiki/vNext-Preview .
索引表达式
索引表达式消除了在使用类数组数据结构时的大部分冗长 (易出错) 的语法.
帽子操作符 (^) 从列表尾部开始计数. 例如, 要获取字符串的最后一个值可以这样写:
var lastCharacter = myString[myString.Length-1];
或者简单地写成:
var lastCharacter = myString[^1];
该特性适用于计算值, 如:
- Index nextIndex = ^(x + 1);
- var nextChar = myString[nextIndex]
范围表达式
范围表达式和索引表达式密切相关, 用 (..) 操作符表示. 下面是一个简单的例子, 获取字符串中的前三个字符:
var s = myString.Substring[0..2];
范围表达式可以结合索引表达式一起使用. 在下面这行代码中, 我们跳过了第一个和最后一个字符.
var s = myString.Substring(1..^1);
范围表达式也适用于 Span.
Span<int> slice = myArray[4...8];
注意, 第一个数值包含, 第二个数值不包含. 因此, 变量 slice 包含元素 4,5,6,7.
要获取一个切片, 包含从某个索引值往后的所有元素, 有两种方法:
- Span<int> rest = myArray[8..];
- Span<int> rest = myArray[8..^0];
类似地, 你可以省略第一个索引:
Span<int> firstFour = myArray[..4];
不用觉得奇怪, 这个语法主要是受 Python 启发, 主要的区别是 C# 不能使用 - 1 表示数组末尾索引, 因为那在. NET 数组中已经有一个意思了. 因此, 我们使用 ^1 语法代替.
GitHub 上提供了 索引和范围的预览 https://github.com/dotnet/csharplang/wiki/vNext-Preview .
IAsyncDisposable
顾名思义, 该接口允许对象暴露一个 DisposeAsync 方法. 与此对应的是新语法 "using async", 它和普通的 using 块一样, 但增加了等待 dispose 调用的功能.
异步枚举
和 IEnumerable 类似, IAsyncEnumerable 让你可以枚举一个长度未知的有限列表. 不过, 配备的枚举器看上去有轻微的差别. 它暴露了两个方法:
- Task<bool> WaitForNextAsync();
- T TryGetNext(out bool success);
该接口有一个有趣的特性, 就是允许你批量读取数据. 你针对批次中的每一个数据项调用 TryGetNext 方法. 当返回 success=false 时, 你可以调用 WaitForNextAsync 获取一个新的批次.
这之所以重要, 是因为大多数进入应用程序的数据要么是按批次, 要么是从网络流入. 当你调用 TryGetNext 时, 数据大多数时候都已经是可用的. 分配一个 Task 对象有点浪费, 除非你用完了输入缓冲区中的数据, 并且仍然希望能够异步等待更多数据.
在理想情况下, 你不会经常直接使用这些接口. 取而代之, 微软希望你使用异步迭代器语法 "foreach await", 我们去年预览过. 它会根据需要处理同步或异步方法的调用.
默认接口方法
这个灵感来自 Java, 颇具争议的特性仍然被考虑加入 C# 8 中. 简单来说, 它让你可以添加新方法及其实现, 达到接口演化的目的. 这样, 新方法不会破坏向后兼容性.
记录
记录是一种快速创建不可变类的语法. 我们第一次看到这个提案是在 2014 年. 当前的例子如下:
class Person(string FirstName, string LastName);
从根本上说, 它完全是一个由构造函数签名定义的类. 对于一个不可变类, 你想要的所有属性和方法都是自动生成的.
来源: http://www.tuicool.com/articles/veaYVfn