Dotnet 6.0,你值得拥有

匿名对象的出现,给我们带来了相当多的方便。在类中,不需要对外输出的结构化数据,都可以做成匿名对象,而
首页 新闻资讯 行业资讯 Dotnet 6.0,你值得拥有

[[421877]]

本文转载自微信公众号「老王Plus」,作者老王Plus的老王。转载本文请联系老王Plus公众号。

最近在研究 Dotnet 6.0 & C# 10,一个字 - 爽!

下面,分享一下新的一些特性给大家。

一、编程语言方面

编程语言升到 C# 10,新东西不少。

1. 属性的 required

看一个简单的例子:

复制

public class User {   public string name { get; set; }   public DateTime dateOfBirth { get; set; } }
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

假设,我们希望 dateOfBirth 字段必须输入。在 C# 9.0 之前,其实我们没有更好的办法。比方:

复制

var myUser = new User() {   name = "WangPlus", }
  • 1.

  • 2.

  • 3.

  • 4.

这样写,编译器是不会给出任何提醒或警告的。因此,我们需要在各个使用的地方,加上字段赋值的检查。

而在最新的语言中,对于这样的需求,增加了一个 required 属性。看代码:

复制

public class User {   public string name { get; set; }   public required DateTime dateOfBirth { get; set; } }
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

这时候,当你实例化 User,而没有给 dateOfBirth 赋值时,编译器会直接扔出异常。

在我写这个文章的同时,刚刚发现这个特性从最新的 preview 里给移除了,似乎微软想把这个放到 C#  11中。目前论坛上吵翻了。我们静待一下结果。

2. 属性的 field

在大多数情况下,我们定义一个类,会采用这种方式:

复制

public class User {   public string name { get; set; }   public DateTime dateOfBirth { get; set; } }
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

但有时候,因为一些需要,需要提前实例化,或者实例化时需要进行某些处理。比方上面的类,我们只想要 dateOfBirth  的日期部分,我们会把类做成这个样子:

复制

public class User {   public string name { get; set; }    private DateTime _dateOfBirth;   public DateTime dateOfBirth    {      get     {       return _dateOfBirth;     }     set     {       _dateOfBirth = value.Date;     }   } }
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

  • 11.

  • 12.

  • 13.

  • 14.

  • 15.

  • 16.

  • 17.

当然,习惯了也没什么麻烦的。不过我们知道,_dateOfBirth 这个私有属性其实是一个中间数据,对外没有用处,但会占用实例资源。

现在,有了一个中间属性,叫 field。代码会变成这样:

复制

public class User {   public string name { get; set; }   public DateTime dateOfBirth { get; set => field = value.Date; } }
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

嗯嗯,可读性就高了不少。

3. 匿名对象的 with

匿名对象的出现,给我们带来了相当多的方便。在类中,不需要对外输出的结构化数据,都可以做成匿名对象,而不需要预先定义一个数据对象。

看代码:

复制

var myUser = new { name = "WangPlus", gender = "Male" };
  • 1.

嗯。真的很方便。

不过,也有不方便的地方,就是匿名对象的传递。比方,我们想创建另一个对象  myUser1,属性还是这些,仅仅需要改变几个属性的值,怎么办?在以前,没有别的办法,只能重写一个:

复制

var myUser1 = new { name = "WangPlus1", gender = "Male" }; /** 或者 **/ var myUser1 = new { name = "WangPlus1", gender = myUser.gender };
  • 1.

  • 2.

  • 3.

可以想象,如果这个匿名对象字段很多的话,就会麻烦的不要不要的。

现在有了 with,这个事情就简单了:

复制

var myUser1 = myUser with { name = "WangPlus1" };
  • 1.

注意,这个写法,不是把 myUser 里的属性改了,而是新生成了一个实例,并传递了 myUser 的全部属性和值到新实例  myUser1,然后才是把一些属性的值改成新的值。

4. 非空参数检查

在我们写一个方法时,成熟的程序员,都会做参数的非空检查:

复制

public string FormatName( string name ) {   if( string.isNullOrEmpty( name ) )     return "ERROR";   /** ... **/ } public string FormatUser( User user ) {   if( user == null )     return "ERROR";   /** ... **/ }
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

  • 11.

  • 12.

做法很正确,但很麻烦,一个套路性的东西,却要不停的写。

现在,有了一个神参数:!,没错,就是叹号。

写法是这样:

复制

public string FormatName( string name! ) {   /** ... **/ } public string FormatUser( User user! ) {     /** ... **/ }
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

加上 ! 后,执行中,程序会自动检查参数的非空状态,如果出现 null,会抛出 ArgumentNullExceptions。

5. global using

这是最爽的一个特性。

以前我们写代码,每个文件前边,都有无数个 using,而且很多 using 都是重复的。

现在,C# 10 提供了一个 global 关键字。从此,using 变成了:

复制

global using System; global using System.Collections.Generic; global using System.Threading.Tasks;
  • 1.

  • 2.

  • 3.

系统会识别 global using 后边的内容会应用于整个项目。因此,在其它文件中,如果需要使用时,可以不写对应的 using  ,直接写代码即可。

再因此,可以把所有的 global using 放到一个单独的文件中,而在其它文件中,不需要再做 using 引用。

同时,如果已经存在 global using,而你的文件中又写了同样库的 using,系统会扔出一个警告。

6. 文件级的命名空间 namespace

这个特性好像没有省了多少事。不过,也算是一个变化。

以前我们做代码时,是这样:

复制

namespace MyNamespace {     public class User     {         public void User()       {             //...Method implementation         }     } }
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

外部调用时,就这么写:

复制

var obj = new MyNamespace.User(); /** 或者 **/ using MyNamespace; var obj = new User();
  • 1.

  • 2.

  • 3.

  • 4.

现在,命令空间的定义改成了:

复制

namespace MyNamespace; public class User {   public void User()       {     //...Method implementation   } }
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

这样写,清爽了一些,缩进的层次也少了一层。当然,调用还是一样的。

二、API方面

API 方面就更多了。在社区里,不停的会有新的 API 爆出来。我就选一些自己感觉有用的来说。

1. 非流式读写文件

流式读写,经常会涉及到中间流,资源浪费不说,写起来也麻烦。

现在可以直接用底层 IO 来读写。方法加到了 File 类中。

复制

var handler = File.OpenHandle("abc.txt"); var length = RandomAccess.GetLength(handler);
  • 1.

  • 2.

2. 强随机数

我们知道,以前的随机数 Random  类是弱随机数,来自于一个算法,并不能做到真正的随机。生成的随机数序列取决于种子,相同的种子会产生相同的随机数序列。

所以,为了取到不同的随机数序列,我们一般这么写:

复制

var rand = new Random( (int)DateTime.Now.Ticks );
  • 1.

当然,一般这样也就够了。但总有特殊的,需要真正的随机数,即强随机数。Dotnet Core 6.0 里,提供了一个  RandomNumberGenerator 的类。

复制

byte[] bytes = RandomNumberGenerator.GetBytes(200); int randomInt = RandomNumberGenerator.GetInt32(0, 10000);
  • 1.

  • 2.

另外需要注意一下,这个类不在 System 空间下,而在 System.Security.Cryptography 里。

3. 多任务的异步 Parallel.ForEachAsync

在多任务中,以前只有一个 Parallel.ForEach 的方法,用来同步执行。这回终于把异步方法 Parallel.ForEachAsync  加进来了,足以可见微软在异步方面的深化决心。

写法还是我们很熟悉的方式,这个切换很容易:

复制

var urls = new [] {   "https://test1.com",   "https://test2.com" }; var client = new HttpClient(); await Parallel.ForEachAsync(urls, async (url, token) => {     HttpResponseMessage response = await client.GetAsync(url); });
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

4. 定时中止异步

这也是个不错的 API。

以前当 await 异步进程时,如果这个进程长时间结束不了,我们只能通过 CancellationToken  来结束。现在,我们有了另一个方式,可以设置一个时间,以 Timeout 的方式结束这个异步进程。

复制

Task someTask = SomeLongRunningTaskAsync(); await someTask.WaitAsync(TimeSpan.FromSeconds(10));
  • 1.

  • 2.

如果你写过 CancellationToken 结束异步的代码,就知道这个 WaitAsync 有多好。

5. ThrowIfNull

这个东西,其实跟上面判断参数是否为空是一件事。当我们在参数据后面加 !来进行为空判断时,实际就是执行的这一句:

复制

public string FormatUser( User user ) {     ArgumentNullException.ThrowIfNull( user ); }
  • 1.

  • 2.

  • 3.

  • 4.

如果对象为空,就抛出一个 ArgumentNullException。

6. 使用直接内存

在以前,使用 unsafe 内存 malloc 时,都是在堆上分配空间。现在有了一个在直接内存分配空间的方法:

复制

using System.Runtime.InteropServices; unsafe {     byte* buffer = (byte*)NativeMemory.Alloc(128);     NativeMemory.Free(buffer); }
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

做嵌入式开发,有福了。

另外,通常使用非托管内容,需要进行大小对齐。所谓对齐就是分配的空间的大小需要是 2 的整指数。通常大家就是算好直接硬写,现在也有了更灵活的方式:

复制

using System.Numerics;  uint bufferSize = 211; if (!BitOperations.IsPow2(bufferSize)) {     bufferSize = BitOperations.RoundUpToPowerOf2(bufferSize); }
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

给一个空间,如果空间大小不是 2 的整指数,就找比这个数大的 2 的整指数。又省事了。

7. 新的计时器

好多文章都把这个计时器称为 Modern Timer,足以可见它的好。

好在哪?这是一个异步的计时器。

以前的计时器 Timer,不管是 System.Timers 下的,还是 System.Threading 下的,或是  System.Windows.Forms 下的,都是同步的计时器,需要用 Tick  的事件绑定来实现回调。这个方式让这个计时器十分依赖上级对象的生命周期,以至于在 UI 编程中,需要用 Invoke 来引入回调响应。

现在这个就简单很多了:

复制

var timer = new PeriodicTimer(TimeSpan.FromSeconds(1)); while (await timer.WaitForNextTickAsync()) {     /** ... **/ }
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

这个写法,看着就舒服。

 

写了很多,但实际上,也只是冰山的一小角。

 

16    2021-09-06 10:22:47    匿名 对象 编程