C# 8.0 新增功能概览

C# 8.0 新增功能概览

新增功能内容: What's new in C# 8.0

本来想稍微翻译一下这份文档的,然而微软早就更新了中文文档 , 那这篇文章就很简单的理解下C#8中的新增内容吧。

C# 8.0 是在 .NetCore 3.x 才支持的,所以如果需要尝试新的语法的话需要手动安装 .NetCore SDK, 或者如果安装了Vs2019的话,更新VS2019.

Readonly 成员

可以将readonly加在结构体的成员中。 这在类成员中我们已经经常使用,比如对于一些DI成员, 只在构造函数中初始化了只会就不会改变, 这样这种成员就适合定义成readonly的:

//...
public class HomeService{
    public readonly IHomeRepository _homeRepo;
    
    public HomeService(IHomeRepository homeRepo){
        _homeRepo = homeRepo;
    }
}

现在,在结构体中的成员也可以定义乘readonly (我都以为原来就可以), 比如定义一个举行:

//...
public struct Rect{
    public double Width {get;set;}
    public double Height {get;set;}
    public readonly double Area => Width * Height;
}

请注意,readonly 修饰符对于只读属性是必需的。 编译器会假设 get 访问器可以修改状态;必须显式声明 readonly

默认接口方法

现在可以将成员添加到接口,并为这些成员提供实现。

这是官方文档上给的一句话定义,简单的来说,就是现在可以在接口中定义属性,方法并且实现了:

interface IShape
{
    public string Name { get; set; }
    public string SayHi()
    {
        return $"this is {Name}";
    }
}
class Rect : IShape
{
    public string Name { get; set; }
}

class Triangle : IShape
{
    public string Name { get; set; }
    public string SayHi()
    {
        return $"这是 {Name}";
    }
}

Switch改进:

Switch 表达式

Switch语句的一种简写形式, 尤其是对于简单的Switch语句效果特别好:

var c = (Color)0;
// Switch 语句
string rgb1;
switch (c)
{
    case Color.Red:
        rgb1 = "f00";
        break;
    case Color.Green:
        rgb1 = "0f0";
        break;
    case Color.Blue:
        rgb1 = "00f";
        break;
    default:
        throw new ArgumentException();
}

// Switch 表达式
string rgb2 = c switch
{
        Color.Red => "f00",
        Color.Green => "0f0",
        Color.Blue => "00f",
        _ => throw new ArgumentException()
};

// Assert.Equal(rgb1, rgb2);

属性模式

可以直接对对象进行Switch, 在Case中判断对象的属性:

var t = new ColorInfo { Color = Color.Red };
_ = t switch
{
        { Color: Color.Red } => t.Chinese = "红色",
        { Color: Color.Green } => t.Chinese = "绿色",
        { Color: Color.Blue } => t.Chinese = "蓝色"
}
Assert.Equal("红色", t.Chinese);

元组模式

跟属性模式差不多, 可以将两个独立的值组成元组进行匹配:

public void TuplePatterns(Color a, Color b)
{
    var res =  (a, b) switch
    {
            (Color.Red, Color.Green) => Color.Yellow,
            (Color.Red, Color.Blue) => Color.Purple,
            (Color.Green, Color.Blue) => Color.Turq,
            _ => throw new ArgumentException()
    };
}

位置模式

这个还没有仔细看,大抵思路跟上面几种差不多,目的也是能够更灵活地进Switch-Case匹配, 具体用法可以参考官方文档:

https://docs.microsoft.com/zh-cn/dotnet/csharp/tutorials/pattern-matching

Using 声明

这个是对原来Using 语句的进一步优化, 省略了大括号, 使用using声明的变量,会在其作用域结束前释放:

static int TestUsingDeclare(IEnumerable<string> lines)
{
    using var file = new System.IO.StreamWriter("file.txt");
    // ....
    // return something;
    // file is disposed here
}

静态本地函数

对于本地函数, 如果它不访问其所在的函数内的任何变量, 可以将这个本地函数声明称静态函数:

int TestStaticLocalMethod()
{
    int y = 5;
    int x = 7;
    return Add(x, y);

    static int Add(int left, int right) => left + right;
}

可处置的 ref 结构(Disposable ref structs)

ref 修饰符声明的 struct 可能无法实现任何接口,因此无法实现 IDisposable。 因此,要能够处理 ref struct,它必须有一个可访问的 void Dispose() 方法。 此功能同样适用于 readonly ref struct 声明

ref struct Point
{
    public int X { get; set; }
    public int Y { get; set; }
    public void Dispose() { }
}

可空引用类型(文档)

示例:

nullable注释上下文中,引用类型的任何变量都被视为不可为空引用类型 。 若要指示一个变量可能为 null,必须在类型名称后面附加 ?,以将该变量声明为可为空引用类型

#nullable enable
string? a = null;
Assert.Equal(0, a?.Length??0);
a = "111";
Assert.Equal(3, a!.Length);

// 下面两句,编译器会报一个警告
string b = null;
Assert.Null(b);
#nullable disable

异步流

可以使用异步流, 创建异步流将返回IAsyncEnumerable类型。

static async IAsyncEnumerable<int> GenerateSequence()
{
    for (int i = 0; i < 20; i++)
    {
        await Task.Delay(100);
        yield return i;
    }
}

var res = 0;
await foreach (var number in GenerateSequence())
{
    Assert.Equal(res++, number);
}

索引和范围

好东西啊! 这个类似于 python 中的切片语法,不过语法看起来稍微复杂一些, 不过怎么说有总比没有好啊。

  • ^ 表示从后面取
  • .. 表示取范围
var arr = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
Assert.Equal(9, arr[^1]);
// 单独访问 arr[^0] 会报错
// 范围选择为左闭右开区间
Assert.Equal(1, arr[0..^0][0]);
Assert.Equal(9, arr[0..^0][^1]);
Assert.Equal(2, arr[1..^1][0]);
Assert.Equal(8, arr[1..^1][^1]);

注: 支持的类型:

  • 支持索引和范围: Array, String, Span, ReadOnlySpan
  • 仅支持索引: List

Null 合并赋值: ??=

a ??= b <==> a = a != null ? a : b

非托管构造类型

如果某个类型是以下类型之一,则它是非托管类型 :

  • sbytebyteshortushortintuintlongulongcharfloatdoubledecimalbool
  • 任何枚举类型
  • 任何指针类型
  • 任何用户定义的 struct 类型,只包含非托管类型的字段,并且在 C# 7.3 及更早版本中,不是构造类型(包含至少一个类型参数的类型)

从 C# 7.3 开始,可使用 unmanaged 约束指定:类型参数为“非指针、不可为 null 的非托管类型”。

从 C# 8.0 开始,仅包含非托管类型的字段的构造结构类型也是非托管类型

嵌套表达式中的 Stackalloc

从 C# 8.0 开始,如果 stackalloc 表达式的结果为 System.SpanSystem.ReadOnlySpan 类型,则可以在其他表达式中使用 stackalloc 表达式:

unsafe
{
    // 默认会返回 int* 类型
    var numbers = stackalloc[] { 1, 2, 3, 4, 5, 6 };
}
Span<int> numbers2 = stackalloc[] { 1, 2, 3, 4, 5, 6 };

内插逐字字符串的增强功能

即字符串声明时[email protected]"dd{ff}"@$"dd{ff}" 都是可以的, 这个如果之前已经习惯了 [email protected] 的就继续留用就好


本文测试代码: https://github.com/zhaokuohaha/SimpleTest/blob/master/SimpleTests/CSharp8Test.cs