未来C#特性列表中的第一位是可空引用类型。 我们在去年首次介绍了这一点,但是要简要回顾一下:默认情况下,所有引用变量,参数和字段将不可为空。 然后,就像值类型一样,如果你希望任何东西都是可以为空的,你必须明确指出通过向类型名称附加一个问号(?)。
这将成为一个可选的功能,现在的想法对于升级到C#8的现有老项目,可空的引用类型是被关闭的。而对于新项目,Microsoft倾向于默认打开该功能。
警告信息将进一步细分为潜在的错误和仅仅是美观的警告。 例如,如果p.MiddleName是一个字符串?,那么这一行将是一个整型警告:
string middleName = p.MiddleName;
由于在引用被取消之前都不会发生危险,所以将其赋值给局部变量是没问题的。 因此,您可以禁用遗留代码上的警告以减少误报数量。
同样,使用此功能的库不会触发警告,因为编译器不知道给定的参数是否应该被视为可为空。
A preview of nullable reference types is available on GitHub.
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}";
}}
在新的语法中,可以消除重复的代码,让代码看起来更紧凑。
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 Pattern Matching
目前可以通过when子句执行属性级模式匹配
case Person p when p.LastName == "Cambell" : return $"{p.FirstName} is not enrolled";
使用“property patterns”,这将被简化为
case Person { LastName: "Cambell"} p : return $"{p.FirstName} is not enrolled";
一个小的增益,但它确实使代码更清晰,并删除了冗余变量名。
Deconstructors(析构) in Patterns
解构体用于将对象分解为其组成部分。它主要用于一个元组的多个任务。使用c# 7.3,您也可以使用模式匹配解构。
在下一个示例中,Person类解构为{FirstName, MiddleName, LastName}。由于我们没有使用MiddleName,所以下划线用作跳过属性的占位符。
case Person ( var fn, _, "Cambell" ) p : return $"{fn} is not enrolled";
Recursive(递归) Patterns
对于我们的下一个模式,我们将Student类析构成{FirstName, MiddleName, LastName, 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 and student.Professor.MiddleName
抽取 student.Professor.LastName 到变量 ln
Index Expressions(索引表达式)
范围表达式消除了处理类似array-like数据结构的大量冗余(和出错)语法。
操作符(^)获取列表的最后数据。例如,要获取一个字符串的最后一个值,您可以写,
var lastCharacter = myString[myString.Length-1];
or 简单写
var lastCharacter = myString[^1];
与下面代码同样的效果
Index nextIndex = ^(x + 1);
var nextChar = myString[nextIndex]
Range Expressions范围表达式
与索引表达式密切相关的是范围表达式,表示为(..)操作符。下面是一个简单的示例,它获取了字符串中的前三个字符。
var s = myString.Substring[0..2];
这可以与索引表达式相结合。在下一行中,我们跳过第一个和最后一个字符。
var s = myString.Substring(1..^1);
也可以使用Span<T>.
Span<int> slice = myArray[4…8];
注意第一个数字是包含的,第二个数字是唯一的。所以,变量里会有元素4到7。
通过索引获取部分数据, 有两种选择
Span<int> rest = myArray[8..];Span<int> rest = myArray[8..^0];
同样,可以省略第一个索引。
Span<int> firstFour = myArray[..4];
在上面的例子中,这种语法受到Python的强烈启发。主要的区别是c#不能使用-1来从数组的末尾进行索引,因为它anet 数组中已经有了意义。因此,我们使用^1语法。
IAsyncDisposable
顾名思义,这个接口允许对象公开一个Dispose Async方法。与此同时,这是“using async”的新语法,它的工作方式就像一个正常使用的块,with the added ability to await on a dispose call.
Asynchronous Enumerators
像IEnumerable <T>一样,IAsyncEnumerable <T>将允许枚举未知长度的有限列表。 匹配的枚举器虽然看起来略有不同。
它公开了两种方法:
Task<bool> WaitForNextAsync();
T TryGetNext(out bool success);
这个接口的一个有趣的功能是它允许你批量读取数据。 您为批处理中的每个项目调用TryGetNext。 当它返回成功= false时,然后调用WaitForNextAsync来获取新的批处理。
这很重要的原因是大多数数据都通过批处理或通过网络流式传输到应用程序。 当您拨打TryGetNext时,数据将在大多数时间可用。 分配一个Task对象将是浪费的,但是如果你确实用完了输入缓冲区中的数据,你仍然希望能够异步地等待更多。
理想情况下,你不会经常直接使用这些接口。 相反,微软希望你使用称为异步迭代器的“foreach await”语法,这是我们去年预览的。 这将根据需要处理调用同步或异步方法。
Default Interface Methods 默认的接口方法
这种受Java启发的有争议的特性仍在C#8中考虑。简而言之,它允许您通过添加具有匹配实现的新方法来演变接口。 这样新方法不会破坏向后兼容性。
Records
Records是快速创建不可变类的语法。 我们在2014年首次看到这个提案。目前的例子如下所示:
class Person(string FirstName, string LastName);
基本上,它是一个完全由构造函数的签名定义的类。您期望从一个不可变类获得的所有属性和方法都是自动生成的。