C#(015):C# 8.0 新特性( NET Framework 4.8 、 .NET Core3.x、.NET Standard 2.1和Visual Studio 2019 )(C # (015): new features of C # 8.0 (net framework 4.8,. Net core3. X,. Net standard 2.1 and visual studio 2019))

C#8.0 于 2019年4月 随 .NET Framework 4.8 与 Visual Studio 2019 一同发布

使用VS2019体检C#8.0新功能:

编辑.csproj文件,添加如下代码

<PropertyGroup>
  <LangVersion>preview</LangVersion>
 </PropertyGroup>

一、可空引用类型(Nullable reference types)

引用类型将会区分是否可空,可以从根源上解决 NullReferenceException。

#nullable enable
        void M(string? s)
        {
            Console.WriteLine(s.Length); // 产生警告:可能为 null
            if (s != null)
            {
                Console.WriteLine(s.Length); // Ok
            }
        }
#nullable disable

二、异步流(Async streams)

考虑到大部分 Api 以及函数实现都有了对应的 async版本,而 IEnumerable和 IEnumerator还不能方便的使用
async/await就显得很麻烦了。
但是,现在引入了异步流,这些问题得到了解决。
我们通过新的 IAsyncEnumerable和 IAsyncEnumerator来实现这一点。同时,由于之前
foreach是基于IEnumerable和 IEnumerator实现的,因此引入了新的语法await foreach来扩展
foreach的适用性。

async Task<int> GetBigResultAsync()
{
    var result = await GetResultAsync();
    if (result > 20) return result; 
    else return -1;
}

async IAsyncEnumerable<int> GetBigResultsAsync()
{
    await foreach (var result in GetResultsAsync())
    {
        if (result > 20) yield return result; 
    }
}

三、范围和下标类型(Ranges and indices)

C# 8.0 引入了 Index 类型,可用作数组下标,并且使用 ^ 操作符表示倒数。
不过要注意的是,倒数是从 1 开始的。

Index i1 = 3;  // 下标为 3
Index i2 = ^4; // 倒数第 4 个元素
int[] a = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
Console.WriteLine($"{a[i1]}, {a[i2]}"); // "3, 6"

除此之外,还引入了 “..” 操作符用来表示范围(注意是左闭右开区间)。

var slice = a[i1..i2]; // { 3, 4, 5 }

关于这个下标从 0 开始,倒数从 1 开始,范围左闭右开。

四、模式匹配表达式(Switch expressions )

典型的模式匹配语句,只不过没有用“match”关键字,而是沿用了了“switch”关键字

object figure = "";
var area = figure switch
{
    Line _ => 0,
    Rectangle r => r.Width * r.Height,
    Circle c => c.Radius * 2.0 * Math.PI,
    _ => throw new UnknownFigureException(figure)
};

C# 8.0中的模式匹配相对C# 7.0来说有了进一步的增强,对于如下类:

class Point
{
public int X { get; }
public int Y { get; }
public Point(int x, int y) => (X, Y) = (x, y);
public void Deconstruct(out int x, out int y) => (x, y) = (X, Y);
}

首先来看C# 7.0中一个经典的模式匹配示例:

static string Display(object o)
{
    switch (o)
    {
        case Point p when p.X == 0 && p.Y == 0:
            return "origin";
        case Point p:
            return $"({p.X}, {p.Y})";
        default:
            return "unknown";
    }
}

在C# 8.0中,它有更加精简的写法。

1、Switch表达式

在C# 8.0中,可以利用新的switch方式成模式匹配:

static string Display(object o) => o switch
{
    Point p when p.X == 0 && p.Y == 0 => "origin",
    Point p                           => $"({p.X}, {p.Y})",
    _                                 => "unknown"
};

它利用一条switch语句完成了模式匹配,第一样看上去要简洁一些。不过,它还有更多更简单的写法。

2、Property patterns

可以直接通过在属性上指定值作为判定条件,

static string Display(object o) => o switch
{
    Point { X: 0, Y: 0 } => "origin",
    Point p              => $"({p.X}, {p.Y})",
    _                    => "unknown"
};

也可以将属性值传递出来。

static string Display(object o) => o switch
{
    Point { X: 0, Y: 0 }         => "origin",
    Point { X: var x, Y: var y } => $"({x}, {y})",
    _                            => "unknown"
};

3、Positional patterns

利用[解构函数](https://docs.microsoft.com/zh-
cn/dotnet/csharp/deconstruct),可以写出更加精简的表达式。

static string Display(object o) => o switch
{
    Point(0, 0)         => "origin",
    Point(var x, var y) => $"({x}, {y})",
    _                   => "unknown"
};

如果没有类型转换,则可以写得更加简单了:

static string Display(Point o) => o switch
{
    (0, 0)         => "origin",
    (var x, var y) => $"({x}, {y})"
};

4、非空判断

如果只是判断空和非空,则有最简单的模式:

{ }  => o.ToString(),
null => "null"

5、Tuple patterns

也支持直接对ValueTuple进行模式匹配,用起来非常灵活。

static State ChangeState(State current, Transition transition, bool hasKey) =>
    (current, transition, hasKey) switch
{
    (Opened, Close, _)     => Closed,
    (Closed, Open,  _)     => Opened,
    (Closed, Lock, true)   => Locked,
    (Locked, Unlock, true) => Closed,
    _ => throw new InvalidOperationException($"Invalid transition")
};

五、递归模式语句(recursive patterns)

现在可以这么写了(patterns 里可以包含 patterns)

IEnumerable<string> GetEnrollees()
{
    foreach (var p in People)
    {
        if (p is Student { Graduated: false, Name: string name }) yield return name;
     }
 }
————————

C#8.0 will follow in April 2019 Net framework 4.8 released with visual studio 2019

Using VSC #8.0 new features:

Edit Csproj file, add the following code

<PropertyGroup>
  <LangVersion>preview</LangVersion>
 </PropertyGroup>

一、可空引用类型(Nullable reference types)

The reference type will distinguish whether it can be null or not, and nullreferenceexception can be solved from the root.

#nullable enable
        void M(string? s)
        {
            Console.WriteLine(s.Length); // 产生警告:可能为 null
            if (s != null)
            {
                Console.WriteLine(s.Length); // Ok
            }
        }
#nullable disable

二、异步流(Async streams)

Considering that most APIs and function implementations have corresponding async versions, and IEnumerable and ienumerator are not easy to use
Async / await is very troublesome.
However, with the introduction of asynchronous flow, these problems have been solved.
We do this through the new iasyncenumerable and iasyncenumerator. At the same time, due to the previous
Foreach is implemented based on IEnumerable and ienumerator, so a new syntax await foreach is introduced to extend
Applicability of foreach.

async Task<int> GetBigResultAsync()
{
    var result = await GetResultAsync();
    if (result > 20) return result; 
    else return -1;
}

async IAsyncEnumerable<int> GetBigResultsAsync()
{
    await foreach (var result in GetResultsAsync())
    {
        if (result > 20) yield return result; 
    }
}

三、范围和下标类型(Ranges and indices)

C # 8.0 introduces the index type, which can be used as an array subscript, and uses the ^ operator to represent the reciprocal.
However, it should be noted that the countdown starts from 1.

Index i1 = 3;  // 下标为 3
Index i2 = ^4; // 倒数第 4 个元素
int[] a = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
Console.WriteLine($"{a[i1]}, {a[i2]}"); // "3, 6"

In addition, “..” is also introduced The operator is used to represent the range (note that it is a left closed right open interval).

var slice = a[i1..i2]; // { 3, 4, 5 }

The subscript starts from 0, the reciprocal starts from 1, and the range is closed on the left and open on the right.

四、模式匹配表达式(Switch expressions )

A typical pattern matching statement uses the “switch” keyword instead of the “match” keyword

object figure = "";
var area = figure switch
{
    Line _ => 0,
    Rectangle r => r.Width * r.Height,
    Circle c => c.Radius * 2.0 * Math.PI,
    _ => throw new UnknownFigureException(figure)
};

Compared with c# 7.0, pattern matching in c# 8.0 is further enhanced. For the following classes:

class Point
{
public int X { get; }
public int Y { get; }
public Point(int x, int y) => (X, Y) = (x, y);
public void Deconstruct(out int x, out int y) => (x, y) = (X, Y);
}

Let’s start with a classic pattern matching example in c# 7.0:

static string Display(object o)
{
    switch (o)
    {
        case Point p when p.X == 0 && p.Y == 0:
            return "origin";
        case Point p:
            return $"({p.X}, {p.Y})";
        default:
            return "unknown";
    }
}

In c# 8.0, it has a more concise way of writing.

1、Switch表达式

In c# 8.0, a new switch mode can be used for pattern matching:

static string Display(object o) => o switch
{
    Point p when p.X == 0 && p.Y == 0 => "origin",
    Point p                           => $"({p.X}, {p.Y})",
    _                                 => "unknown"
};

It uses a switch statement to complete pattern matching. The first one looks simpler. However, there are more and simpler ways to write it.

2、Property patterns

You can directly specify the value on the attribute as the judgment condition,

static string Display(object o) => o switch
{
    Point { X: 0, Y: 0 } => "origin",
    Point p              => $"({p.X}, {p.Y})",
    _                    => "unknown"
};

You can also pass the attribute value.

static string Display(object o) => o switch
{
    Point { X: 0, Y: 0 }         => "origin",
    Point { X: var x, Y: var y } => $"({x}, {y})",
    _                            => "unknown"
};

3、Positional patterns

Using [deconstruction function]( https://docs.microsoft.com/zh-
Cn / dotnet / CSharp / deconstruct), you can write more concise expressions.

static string Display(object o) => o switch
{
    Point(0, 0)         => "origin",
    Point(var x, var y) => $"({x}, {y})",
    _                   => "unknown"
};

If there is no type conversion, it can be written more simply:

static string Display(Point o) => o switch
{
    (0, 0)         => "origin",
    (var x, var y) => $"({x}, {y})"
};

4. Non null judgment

If you only judge null and non null, there is the simplest mode:

{ }  => o.ToString(),
null => "null"

5、Tuple patterns

It also supports direct pattern matching of valuetuple, which is very flexible to use.

static State ChangeState(State current, Transition transition, bool hasKey) =>
    (current, transition, hasKey) switch
{
    (Opened, Close, _)     => Closed,
    (Closed, Open,  _)     => Opened,
    (Closed, Lock, true)   => Locked,
    (Locked, Unlock, true) => Closed,
    _ => throw new InvalidOperationException($"Invalid transition")
};

五、递归模式语句(recursive patterns)

Now it can be written like this (patterns can be included in patterns)

IEnumerable<string> GetEnrollees()
{
    foreach (var p in People)
    {
        if (p is Student { Graduated: false, Name: string name }) yield return name;
     }
 }