C#新特性

 匿名对象的 with

可以用 with 来根据已有的匿名对象创建新的匿名对象了:

var x = new { A = 1, B = 2 };
var y = x with { A = 3 };

常量字符串插值

你可以给 const string 使用字符串插值了,非常方便:

const string x = "hello";
const string y = $"{x}, world!";

CallerArgumentExpression

现在,CallerArgumentExpression 这个 attribute 终于有用了。借助这个 attribute,编译器会自动填充调用参数的表达式字符串,例如:

void Foo(int value, [CallerArgumentExpression("value")] string? expression = null)
{
    Console.WriteLine(expression + " = " + value);
}

当你调用 Foo(4 + 5) 时,会输出 4 + 5 = 9。这对测试框架极其有用,因为你可以输出 assert 的原表达式了:

static void Assert(bool value, [CallerArgumentExpression("value")] string? expr = null)
{
    if (!value) throw new AssertFailureException(expr);
}
 

条件 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<TResult> 的方法中。
  • ref 结构变量不能在迭代器中使用。

Span<T> 和 System.ReadOnlySpan<T> 本身是一个 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<T> 来安全的访问,同样的,这么做可以做到 0 GC 压力。

stackalloc 允许任何的值类型结构,但是要注意,Span<T> 目前不支持 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<TResult> 类型。

 

数字文本语法改进

二进制文本和数字分隔符 

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<T> 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);
}

同步流在迭代集合的过程中会阻塞调用线程,而且需要给所有数据分配存储空间后再返回

异步流解决了上述两个问题

 

异步可释放

实现 System.IAsyncDisposable 接口的异步可释放类型。 可使用 await using 语句来处理异步可释放对象。

public 无参数的 DisposeAsync() 方法在 await using 语句中隐式调用,其用途是释放非托管资源,执行常规清理,以及指示终结器(如果存在)不必运行。 释放与托管对象关联的内存始终是垃圾回收器的域。 因此,它具有标准实现:

public class ExampleAsyncDisposable : IAsyncDisposable, IDisposable
{
    private Utf8JsonWriter? _jsonWriter = new(new MemoryStream());

    public void Dispose()
    {
        Dispose(disposing: true);
        GC.SuppressFinalize(this);
    }

    public async ValueTask DisposeAsync()
    {
        await DisposeAsyncCore().ConfigureAwait(false);

        Dispose(disposing: false);
#pragma warning disable CA1816 // Dispose methods should call SuppressFinalize
        GC.SuppressFinalize(this);
#pragma warning restore CA1816 // Dispose methods should call SuppressFinalize
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            _jsonWriter?.Dispose();
            _jsonWriter = null;
        }
    }

    protected virtual async ValueTask DisposeAsyncCore()
    {
        if (_jsonWriter is not null)
        {
            await _jsonWriter.DisposeAsync().ConfigureAwait(false);
        }

        _jsonWriter = null;
    }
}

DisposeAsyncCore() 方法将异步释放托管资源,因此不希望也同步释放这些资源。 因此,调用 Dispose(false) 而非 Dispose(true)

class ExampleConfigureAwaitProgram
{
    static async Task Main()
    {
        var exampleAsyncDisposable = new ExampleAsyncDisposable();
        await using (exampleAsyncDisposable.ConfigureAwait(false))
        {
            // Interact with the exampleAsyncDisposable instance.
        }

        Console.ReadLine();
    }
}

在创建和使用实现 IAsyncDisposable 的多个对象的情况下,残存错误条件中具有 ConfigureAwait 的堆叠 await using 语句可能会阻止调用 DisposeAsync()。 若要确保始终调用 DisposeAsync(),应避免堆叠。

class DoNotDoThisProgram
{
    static async Task Main()
    {
        var objOne = new ExampleAsyncDisposable();
        // Exception thrown on .ctor
        var objTwo = new AnotherAsyncDisposable();

        await using (objOne.ConfigureAwait(false))
        await using (objTwo.ConfigureAwait(false))
        {
            // Neither object has its DisposeAsync called.
        }

        Console.ReadLine();
    }
}

 如果从 AnotherAsyncDisposable 构造函数引发异常,则 objOne 不会正确释放。因此对象应该各自await using 

 

索引和范围

 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

 

 

已知创建对象的类型时,可在 new 表达式中省略该类型

private List<WeatherObservation> _observations = new();

var forecast = station.ForecastFor(DateTime.Now.AddDays(2), new());

 

从 C# 9.0 开始,可将 static 修饰符添加到 Lambda 表达式匿名方法。 静态 Lambda 表达式类似于 static 局部函数:静态 Lambda 或匿名方法无法捕获局部变量或实例状态。 static 修饰符可防止意外捕获其他变量。

 

posted @ 2020-08-05 20:07  yetsen  阅读(235)  评论(0编辑  收藏  举报