原文地址: https://blogs.msdn.microsoft.com/dotnet/2018/12/05/take-c-8-0-for-a-spin/git
昨天咱们宣布了Visual Studio 2019的第一个预览版(使用Visual Studio 2019提升每一个开发人员的工做效率)和.NET Core 3.0(宣布.NET Core 3预览1和开源Windows桌面框架)。github
其中一个使人兴奋的方面是你可使用C#8.0中的一些功能!在这里,我将带您进行一次导游,了解您能够在预览中尝试的三种新的C#功能。并不是全部C#8.0功能均可用。若是您想了解全部主要功能,请阅读最近发布Building C# 8.0,或查看Channel 9或YouTube。编程
首先,下载并安装.NET Core 3.0的预览版1和Visual Studio的2019的预览版1。在Visual Studio中,确保选择工做负载“.NET Core跨平台开发”(若是您忘记了,能够稍后经过打开Visual Studio安装程序并单击Visual Studio 2019预览频道上的“修改”来添加它)。windows
启动Visual Studio 2019预览版,建立新项目,而后选择“Console App(.NET Core)”做为项目类型。数组
项目启动并运行后,将其目标框架更改成.NET Core 3.0(在解决方案资源管理器中右键单击该项目,选择“属性”并使用“应用程序”选项卡上的下拉菜单)。而后选择C#8.0做为语言版本(在项目页面的Build选项卡上单击“Advanced ...”并选择“C#8.0(beta)”)。安全
如今,您能够轻松得到全部语言功能和支持框架类型!数据结构
可空引用类型功能旨在警告您代码中的null不安全行为。既然咱们以前没有这样作过,那么如今就开始改变吧!为避免这种状况,您须要选择加入该功能。框架
不过,在咱们开启它以前,让咱们写一些很是糟糕的代码:异步
using static System.Console; class Program { static void Main(string[] args) { string s = null; WriteLine($"The first letter of {s} is {s[0]}"); } }
若是你运行它,你固然会获得一个空引用异常。你陷入了黑洞!你怎么知道不要在特定的地方间接引用s?嗯,由于在前一行分配了null。可是在现实生活中,它可能不是在前一行,而是在你编写代码的三年后在地球另外一端运行的其余人程序集中。你怎么知道不写那个?这是可空引用类型要回答的问题!因此让咱们打开它们吧!async
对于一个新项目,你应该当即打开它们。事实上,我认为它们应该在新项目中默认启用,但咱们在预览中没有这样作。打开它们的方法是将如下行添加到.csproj文件中,例如在切换到上面的C#8.0时刚刚插入的LanguageVersion以后:
<NullableReferenceTypes>true</NullableReferenceTypes>
保存.csproj文件并返回到您的程序:发生了什么?你有两个警告!每一个表明一个功能的“一半”。让咱们依次看看它们。第一个是null这一行:
string s = null;
它抱怨你将null赋给“不可空类型”:啥?!?当打开该功能时,在普通的引用类型中再也不欢迎使用null,例如string!由于,你知道吗,null不是一个字符串!咱们一直伪装在过去的五十年在面向对象编程,但实际上null并非一个对象:这就是为何每当你试图将它作为对象时一切都会爆炸!
因此很少说:null是禁止的,除非你要求它。你是怎么要求的?经过使用可空的引用类型,例如string?。尾随问号表示容许null:
string? s = null;
警告消失了:咱们已明确表达了此变量保持null的意图,因此如今没问题了。
直到下一行代码!在该行:
WriteLine($"The first letter of {s} is {s[0]}");
它抱怨s中s[0],你可能会间接引用一空引用。果真:是!干得好,编译器!你怎么解决它?嗯,这几乎取决于你 - 不管何种方式你得修复它!让咱们尝试初学者的方法, 只在s非null时执行该行:
if (s != null) WriteLine($"The first letter of {s} is {s[0]}");
警告消失了!为何?由于编译器能够看到,只有s不是null时才会走后面的代码。它实际上进行了全流分析,跟踪每行代码中的每一个变量,以便密切关注它多是null的和可能不是的位置。它会监视您的测试和做业,并进行簿记(bookkeeping)。
咱们试试另外一种方法:
WriteLine($"The first letter of {s} is {s?[0] ?? '?'}");
这使用null条件索引运算符s?[0],它避免了间接引用,若是s为null ,则生成null。如今咱们有一个可空的char?,可是null合并运算符?? '?'替换null值为字符 '?'。所以避免了全部null间接引用。编译器很高兴,没有给出警告。
正如您所看到的,该功能可让您在编写代码时保持诚实:它会强制您在系统中使用null时经过使用可空的引用类型来表达您的意图。而且一旦出现null,它就会强制您负责任地处理它,让您在存在可能间接引用null值以触发空引用异常的风险时进行检查。
你如今彻底null安全了吗?没有。有几种方法可使null值漏掉并致使空引用异常:
new string[10]
,咱们建立一个充满null值的数组,类型为非null字符串。咱们不会对此发出警告,由于编译器如何跟踪您初始化全部数组元素?但总的来讲,若是你普遍使用这个功能(即在任何地方打开它),它应该照顾绝大多数的空引用。
毫无疑问地,咱们打算在现有代码上开始使用该功能!一旦打开它,您可能会收到不少警告。其中一些实际上表明了问题:是的,你发现了一个错误!其中一些可能有点烦人; 你的代码显然是null安全的,你只是没有工具来表达你的意图:你没有可空的引用类型!例如,在咱们开始的行:
string s = null;
这在现有代码中将很是广泛!正如你所看到的那样,咱们也确实在下一行发出了警告,咱们试图间接引用它。所以,从安全的角度来看,此处的赋值警告严格来讲是多余的:它使您在新代码中保持诚实,但修复现有代码中的全部事件并不会使其更安全。对于这种状况,咱们正在处理一种模式,其中某些警告被关闭,当它不影响空安全性时,所以升级现有代码不那么使人生畏。
另外一个有助于升级的功能是,您可使用编译器指令#nullable enable
和#nullable disable
在代码中“本地”打开或关闭该功能。这样你就能够逐步完成你的项目并逐步处理注释和警告。
要了解更多关于可空引用类型检查出Overview of Nullable types和Introduction to nullable tutorial。
为了更深刻的设计理由,去年我在C#中写了一篇帖子Introducing Nullable Reference Types in C#。
若是您想让本身沉浸在设计工做的平常工做中,请查看GitHub上的Language Design Notes,或者Nullable Reference Types Specification。
使用索引数据结构时,C#的表现力愈来愈强。曾经想要简单的语法来切出数组,字符串或span的一部分吗?如今你能够!
继续将您的程序更改成如下内容:
using System.Collections.Generic; using static System.Console; class Program { static void Main(string[] args) { foreach (var name in GetNames()) { WriteLine(name); } } static IEnumerable<string> GetNames() { string[] names = { "Archimedes", "Pythagoras", "Euclid", "Socrates", "Plato" }; foreach (var name in names) { yield return name; } } }
让咱们来看看迭代名字数组的那段代码。修改foreach以下:
foreach (var name in names[1..4])
看起来咱们正在迭代名字1到4。事实上代码运行时也确实如此!终点是排外的,即不包括元素4。1..4其实是一个范围表达式,它没必要像该处同样,做为索引操做的一部分出现。它有一种本身的类型,叫作Range。若是咱们想要的话,咱们能够把它拉到本身的变量中,它会起到一样的做用:
Range range = 1..4; foreach (var name in names[range])
范围表达式的终点没必要是整数。事实上,它们属于一种类型,叫Index,可由非负数转换得来。可是你也可使用一个新的^运算符建立Index,意思是“从末尾”。因此^1是从末尾开始1个:
foreach (var name in names[1..^1])
这会在数组的每一端去除一个元素,产生一个带有中间三个元素的数组。
范围表达式能够在任一端或两端打开。..^1
与0..^1
相同。1..
与1..^0
相同。而且..
与0..^0
相同:从头至尾。试试吧!尝试在Range的两端混合使用“从开始”和“从末尾”的Index,看看会发生什么。
范围不只仅适用于索引器。例如,咱们计划有重载string.SubString
,SPan<T>.Slice
以及使用Range参数的AsSpan
扩展方法。这些不在.NET Core 3.0预览中。
IEnumerable<T>
在C#中扮演着特殊的角色。“IEnumerables”表明各类不一样的数据序列,而且语言具备用于消费和生成它们的特殊构造。
正如咱们在当前的程序中看到的那样,它们经过foreach声明来消费,该声明涉及获取枚举器的苦差事,反复推动它,沿途提取元素,最后处理枚举器。而且可使用迭代器生成它们:yield return按消费者要求产生元素。但二者都是同步的:当结果被请求时最好已经准备就绪,不然就会阻塞线程!
async和await加入到C#中用来处理当结果被请求时不必定准备好的状况。它们能够异步await,而且线程能够在其可用以前执行其余操做。但这仅适用于单个值,而不适用于随时间逐渐和异步生成的序列,例如来自IoT传感器的测量值或来自服务的流数据。
异步流在C#中将异步和枚举结合在一块儿!让咱们看看,经过逐步“异步”咱们当前的程序。
首先,让咱们在文件的顶部添加另外一个using指令:
using System.Threading.Tasks;
如今让咱们经过在yield return名字以前增长一个异步延迟来模拟GetNames作了一些异步工做:
await Task.Delay(1000); yield return name;
固然,咱们获得了一个错误: 只能在async方法中使用await。因此咱们让它异步:
static async IEnumerable<string> GetNames()
如今咱们被告知咱们没有为异步方法返回正确的类型,这是公平的。但除了一般的Task东西以外,此次咱们的类型列表中有了一个新的候选能够返回:IAsyncEnumerable
static async IAsyncEnumerable<string> GetNames()
就像咱们已经生成了一个异步字符串流!根据命名指南,让咱们重命名GetNames为GetNamesAsync。
static async IAsyncEnumerable<string> GetNamesAsync()
如今咱们在Main方法中的这一行获得一个错误:
foreach (var name in GetNamesAsync())
不知道如何foreach一个IAsyncEnumerable<T>
。这是由于异步流的foreach须要显式使用await关键字:
await foreach (var name in GetNamesAsync())
这是foreach的异步版本:采用异步流并等待每一个元素!固然它只能在异步方法中这样作,因此咱们必须使咱们的Main方法异步。幸运的是,C#7.2增长了对它的支持:
static async Task Main(string[] args)
如今全部的混乱都消失了,程序是正确的。可是若是你尝试编译并运行它,你会获得一些使人尴尬的错误。那是由于咱们搞砸了一下,并无彻底对齐.NET Core 3.0和Visual Studio 2019的预览。具体来讲,有一种实现类型,异步迭代器利用它与编译器指望的不一样。
您能够经过向项目添加单独的源文件来修复此问题,其中包含此桥接代码。再次编译,一切都应该工做得很好。
请让咱们知道你的想法!若是您尝试这些功能并了解如何改进它们,请使用Visual Studio 2019预览中的反馈按钮。预览的整个目的是根据现实用户手中的功能如何进行最后一次校订,因此请告诉咱们!
编码快乐,
Mads Torgersen,C#设计负责人