C#新特性


 条件 ref 表达式

条件表达式可能生成 ref 结果而不是值。 例如,你将编写以下内容以检索对两个数组之一中第一个元素的引用

ref var r = ref (arr != null ? ref arr[0] : ref otherArr[0]);

 ref struct

 ref 结构类型的实例在堆栈上分配,并且不能转义到托管堆,因此不会给 GC 带来任何的压力。相对的,使用中就会有不能逃逸出栈的强制限制。

编译器将 ref 结构类型的使用限制如下:

  • ref 结构不能是数组的元素类型。
  • ref 结构不能是类或非 ref 结构的字段的声明类型。
  • ref 结构不能实现接口。
  • ref 结构不能被装箱为 System.ValueType 或 System.Object。
  • ref 结构不能是类型参数。
  • ref 结构变量不能由 lambda 表达式或本地函数捕获。
  • ref 结构变量不能在 async 方法中使用。 但是,可以在同步方法中使用 ref 结构变量,例如,在返回 Task 或 Task 的方法中。
  • ref 结构变量不能在迭代器中使用。

Span 和 System.ReadOnlySpan 本身是一个 readonly ref struct,成功的封装出了安全且高性能的内存访问操作,且可在大多数情况下代替指针而不损失任何的性能。

ref struct MyStruct
{
    public int Value { get; set; }
}
    
class RefStructGuide
{
    static void Test()
    {
        MyStruct x = new MyStruct();
        x.Value = 100;
        Foo(x); // ok
        Bar(x); // error, x cannot be boxed
    }

    static void Foo(MyStruct x) { }
    
    static void Bar(object x) { }
}

通过 stackalloc 直接在栈上分配内存,并使用 Span 来安全的访问,同样的,这么做可以做到 0 GC 压力。

stackalloc 允许任何的值类型结构,但是要注意,Span 目前不支持 ref struct 作为泛型参数,因此在使用 ref struct 时需要直接使用指针。

static unsafe void RefStructAlloc()
    {
        MyStruct* x = stackalloc MyStruct[10];
        for (int i = 0; i < 10; i++)
        {
            *(x + i) = new MyStruct { Value = i };
        }
    }

    static void StructAlloc()
    {
        Span<int> x = stackalloc int[10];
        for (int i = 0; i < x.Length; i++)
        {
            x[i] = i;
        }
    }

性能敏感时对于频繁调用的函数使用 SkipLocalsInit

C# 为了确保代码的安全会将所有的局部变量在声明时就进行初始化,无论是否必要。一般情况下这对性能并没有太大影响,但是如果你的函数在操作很多栈上分配的内存,并且该函数还是被频繁调用的,那么这一消耗的副作用将会被放大变成不可忽略的损失。

因此你可以使用 SkipLocalsInit 这一特性禁用自动初始化局部变量的行为。

[SkipLocalsInit]
unsafe static void Main()
{
    Guid g;
    Console.WriteLine(*&g);
}

上述代码将输出不可预期的结果,因为 g 并没有被初始化为 0。另外,访问未初始化的变量需要在 unsafe 上下文中使用指针进行访问。

表达式体

成员函数、只读属性、构造函数、get 和 set 访问器。

// Expression-bodied constructor
public ExpressionMembersExample(string label) => this.Label = label;

private string label;

// Expression-bodied get / set accessors.
public string Label
{
    get => label;
    set => this.label = value ?? "Default label";
}

throw 表达式

throw 可以用作表达式和语句。 这允许在以前不支持的上下文中引发异常。 这些方法包括条件表达式、null 合并表达式和一些 lambda 表达式。

string arg = args.Length >= 1 ? args[0] :
                              throw new ArgumentException("You must supply an argument");

name = value ??
        throw new ArgumentNullException(paramName: nameof(value), message: "Name cannot be null");

DateTime ToDateTime(IFormatProvider provider) =>
         throw new InvalidCastException("Conversion to a DateTime is not supported.");

ValueTask

从异步方法返回 Task 对象可能在某些路径中导致性能瓶颈。 Task 是引用类型,因此使用它意味着分配对象。

ValueTask 此增强功能对于库作者最有用,可避免在性能关键型代码中分配 Task

需要添加 NuGet 包 System.Threading.Tasks.Extensions 才能使用 ValueTask 类型。

数字文本语法改进

二进制文本和数字分隔符 

public const int OneHundredTwentyEight = 0b1000_0000;

public const long BillionsAndBillions = 100_000_000_000;

public const double AvogadroConstant = 6.022_140_857_747_474e23;

int binaryValue = 0b_0101_0101;

 

默认文本表达式

针对默认值表达式的一项增强功能。 这些表达式将变量初始化为默认值。 过去会这么编写:

Func<string, bool> whereClause = default(Func<string, bool>);

现在,可以省略掉初始化右侧的类型:

Func<string, bool> whereClause = default;

增强的泛型约束

可以将类型 System.Enum 或 System.Delegate 指定为类型参数的基类约束。

现在也可以使用新的 unmanaged 约束来指定类型参数必须是不可为 null 的“非托管类型”。
非托管类型 :
? sbyte、byte、short、ushort、int、uint、long、ulong、char、float、double、decimal 或 bool
? 任何枚举类型
? 任何指针类型
? 任何用户定义的 struct 类型,只包含非托管类型的字段

    public struct Coords where T : unmanaged
    {
        public T X;
        public T Y;
    }

        var c = new Coords<int>();

默认接口方法

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

异步流

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

await foreach (var number in GenerateSequence())
{
    Console.WriteLine(number);
}

索引和范围

 0 索引与 sequence[0] 相同。 ^0 索引与 sequence[sequence.Length] 相同。 请注意,sequence[^0] 不会引发异常

对于任何数字 n,索引 ^n 与 sequence.Length - n 相同。^1 索引最后

 [..]表示整个范围,相当于范围 [0..^0] 

words[1..4]包括 words[1] 到 words[3]。 元素 words[4] 不在该范围内

var allWords = words[..]; // contains "The" through "dog".
var firstPhrase = words[..4]; // contains "The" through "fox"
var lastPhrase = words[6..]; // contains "the", "lazy" and "dog"

 

Null 合并赋值

List<int> numbers = null;
int? i = null;

numbers ??= new List<int>();
numbers.Add(i ??= 17);
numbers.Add(i ??= 20);

Console.WriteLine(string.Join(" ", numbers));  // output: 17 17
Console.WriteLine(i);  // output: 17
C