【Visual Studio 2019 And .NET Core 3.0 】C# 8.0的新特性

“.NET Core 3.x”和“.NET Standard 2.1”支持 C# 8.0 。

Alternative interpolated verbatim  strings (可选择的内插逐字字符串)


 In C# 8.0 we added a feature that permits(许可) an interpolated verbatim string to be introduced with the characters @$" or the characters $@". This is a placeholder(n. 占位符) for its specification.

(内插逐字字符串中 $ 和 @ 标记的顺序可以任意安排:$@"..." 和 @$"..." 均为有效的内插逐字字符串。 在早期 C# 版本中,$ 标记必须出现在 @ 标记之前。)

 Async Streams (异步流)


 C# has support for iterator(n. 迭代器;迭代程序) methods and async methods, but no support for a method that is both an iterator and an async method. We should rectify(vt. 改正) this by allowing for await to be used in a new form of async iterator, one that returns an IAsyncEnumerable<T> or IAsyncEnumerator<T> rather than an IEnumerable<T> or IEnumerator<T>, with IAsyncEnumerable<T> consumable in a new await foreach. An IAsyncDisposable interface is also used to enable asynchronous cleanup.

IAsyncDisposable

There has been much discussion of IAsyncDisposable (e.g. https://github.com/dotnet/roslyn/issues/114) and whether it's a good idea. However, it's a required concept to add in support of async iterators. Since finally blocks may contain awaits, and since finally blocks need to be run as part of disposing of iterators, we need async disposal(n. 处理;支配;清理;安排). It's also just generally useful any time cleaning up of resources might take any period of time, e.g. closing files (requiring flushes), deregistering(撤销……的登记) callbacks and providing a way to know when deregistration has completed, etc.

The following interface is added to the core .NET libraries (e.g. System.Private.CoreLib / System.Runtime):

namespace System
{
    public interface IAsyncDisposable
    {
        ValueTask DisposeAsync();
    }
}

As with Dispose, invoking DisposeAsync multiple times is acceptable, and subsequent invocations(n. 装置,[计] 调用) after the first should be treated as nops(n. 空操作), returning a synchronously completed successful task (DisposeAsync need not be thread-safe, though, and need not support concurrent invocation). Further, types may implement both IDisposable and IAsyncDisposable, and if they do, it's similarly acceptable to invoke Dispose and then DisposeAsync or vice versa(反之亦然), but only the first should be meaningful and subsequent invocations of either should be a nop. As such, if a type does implement both, consumers are encouraged to call once and only once the more relevant method based on the context, Dispose in synchronous contexts and DisposeAsync in asynchronous ones.

(与Dispose一样,可以多次调用DisposeAsync,并且将第一次调用之后的后续调用视为空操作,返回同步完成的成功任务(但是DisposeAsync不必是线程安全的,并且不需要支持并发调用)。 此外,类型可以同时实现IDisposable和IAsyncDisposable,并且如果这样做,则先调用Dispose然后再调用DisposeAsync也是可以接受的,反之亦然,但是类型中只有第一个有意义,随后的任何一个调用都应该是空操作。 因此,如果一个类型确实实现了这两种类型,则鼓励使用者一次仅调用一次基于上下文的更相关方法,即在同步上下文中进行Dispose,在异步上下文中进行DisposeAsync。)

IAsyncEnumerable / IAsyncEnumerator

Two interfaces are added to the core .NET libraries:

namespace System.Collections.Generic
{
    public interface IAsyncEnumerable<out T>
    {
        IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = default);
    }

    public interface IAsyncEnumerator<out T> : IAsyncDisposable
    {
        ValueTask<bool> MoveNextAsync();
        T Current { get; }
    }
}

Typical consumption (without additional language features) would look like:

IAsyncEnumerator<T> enumerator = enumerable.GetAsyncEnumerator();
try
{
    while (await enumerator.MoveNextAsync())
    {
        Use(enumerator.Current);
    }
}
finally { await enumerator.DisposeAsync(); }

Viable alternative:

namespace System.Collections.Generic
{
    public interface IAsyncEnumerable<out T>
    {
        IAsyncEnumerator<T> GetAsyncEnumerator();
    }

    public interface IAsyncEnumerator<out T> : IAsyncDisposable
    {
        ValueTask<bool> WaitForNextAsync();
        T TryGetNext(out bool success);
    }
}

TryGetNext is used in an inner loop to consume items with a single interface call as long as they're available synchronously. When the next item can't be retrieved synchronously, it returns false, and any time it returns false, a caller must subsequently invoke WaitForNextAsync to either wait for the next item to be available or to determine that there will never be another item. Typical consumption (without additional language features) would look like:

IAsyncEnumerator<T> enumerator = enumerable.GetAsyncEnumerator();
try
{
    while (await enumerator.WaitForNextAsync())
    {
        while (true)
        {
            int item = enumerator.TryGetNext(out bool success);
            if (!success) break;
            Use(item);
        }
    }
}
finally { await enumerator.DisposeAsync(); }

 foreach

foreach will be augmented to support IAsyncEnumerable<T> in addition to its existing support for IEnumerable<T>. And it will support the equivalent of IAsyncEnumerable<T> as a pattern if the relevant members are exposed publicly, falling back to using the interface directly if not, in order to enable struct-based extensions that avoid allocating as well as using alternative awaitables as the return type of MoveNextAsync and DisposeAsync.

Syntax

Using the syntax:

foreach (var i in enumerable)

C# will continue to treat enumerable as a synchronous enumerable, such that even if it exposes the relevant APIs for async enumerables (exposing the pattern or implementing the interface), it will only consider the synchronous APIs.

To force foreach to instead only consider the asynchronous APIs, await is inserted as follows:

await foreach (var i in enumerable)

Async Iterators

static IEnumerable<int> MyIterator()
{
    try
    {
        for (int i = 0; i < 100; i++)
        {
            Thread.Sleep(1000);
            yield return i;
        }
    }
    finally
    {
        Thread.Sleep(200);
        Console.WriteLine("finally");
    }
}
static async IAsyncEnumerable<int> MyIterator()
{
    try
    {
        for (int i = 0; i < 100; i++)
        {
            await Task.Delay(1000);
            yield return i;
        }
    }
    finally
    {
        await Task.Delay(200);
        Console.WriteLine("finally");
    }
}

LINQ

public static IAsyncEnumerable<TResult> Select<TSource, TResult>(this IAsyncEnumerable<TSource> source, Func<TSource, TResult> func);
public static IAsyncEnumerable<T> Where(this IAsyncEnumerable<T> source, Func<T, bool> func);

Async using declaration (using 声明)


 In C# 8.0 we added support for an async using statements. There are two forms. One has a block body that is the scope of the using declaratation. The other declares a local and is implicitly(adv.隐含地;言外地) scoped to the end of the block. This is a placeholder(有效零;占位成分;) for their specification.

static int WriteLinesToFile(IEnumerable<string> lines)
{
    // We must declare the variable outside of the using block
    // so that it is in scope to be returned.
    int skippedLines = 0;
    using (var file = new System.IO.StreamWriter("WriteLines2.txt"))
    {
        foreach (string line in lines)
        {
            if (!line.Contains("Second"))
            {
                file.WriteLine(line);
            }
            else
            {
                skippedLines++;
            }
        }
        return skippedLines;
    } // file is disposed here
}

Override with constraints


  In C# 8.0, we added a feature to permit(v.允许;许可;) the specification of certain type parameter constraints in an override method declaration. This is a placeholder for its specification.

Unmanaged constructed types


 In C# 8.0, we extended the concept of an unmanaged type to include constructed (generic) types. This is a placeholder for its specification.

Does an override in an interface introduce a new member? (closed)

 There are a few ways to observe whether an override declaration introduces a new member or not.

interface IA
{
    void M(int x) { }
}
interface IB : IA
{
    override void M(int y) { }
}
interface IC : IB
{
    static void M2()
    {
        M(y: 3); // permitted?
    }
    override void IB.M(int z) { } // permitted? What does it override?
}

Properties with a private accessor (closed)

 We say that private members are not virtual, and the combination of virtual and private is disallowed. But what about a property with a private accessor?

interface IA
{
    public virtual int P
    {
        get => 3;
        private set => { }
    }
}

 Is this allowed? Is the set accessor here virtual or not? Can it be overridden where it is accessible? Does the following implicitly implement only the get accessor?

class C : IA
{
    public int P
    {
        get => 4;
        set { }
    }
}

 Is the following presumably(adv.大概,推测起来,) an error because IA.P.set isn't virtual and also because it isn't accessible?

class C : IA
{
    int IA.P
    {
        get => 4;
        set { }
    }
}

Base Interface Invocations, round 2 (closed)

Our previous "resolution" to how to handle base invocations doesn't actually provide sufficient expressiveness. It turns out that in C# and the CLR, unlike Java, you need to specify both the interface containing the method declaration and the location of the implementation you want to invoke.

I propose the following syntax for base calls in interfaces. I’m not in love with it, but it illustrates what any syntax must be able to express:

interface I1 { void M(); }
interface I2 { void M(); }
interface I3 : I1, I2 { void I1.M() { } void I2.M() { } }
interface I4 : I1, I2 { void I1.M() { } void I2.M() { } }
interface I5 : I3, I4
{
    void I1.M()
    {
        base<I3>(I1).M(); // calls I3's implementation of I1.M
        base<I4>(I1).M(); // calls I4's implementation of I1.M
    }
    void I2.M()
    {
        base<I3>(I2).M(); // calls I3's implementation of I2.M
        base<I4>(I2).M(); // calls I4's implementation of I2.M
    }
}

 If there is no ambiguity(n.模糊,含糊,不明确,), you can write it more simply

interface I1 { void M(); }
interface I3 : I1 { void I1.M() { } }
interface I4 : I1 { void I1.M() { } }
interface I5 : I3, I4
{
    void I1.M()
    {
        base<I3>.M(); // calls I3's implementation of I1.M
        base<I4>.M(); // calls I4's implementation of I1.M
    }
}

Or

interface I1 { void M(); }
interface I2 { void M(); }
interface I3 : I1, I2 { void I1.M() { } void I2.M() { } }
interface I5 : I3
{
    void I1.M()
    {
        base(I1).M(); // calls I3's implementation of I1.M
    }
    void I2.M()
    {
        base(I2).M(); // calls I3's implementation of I2.M
    }
}

Or

interface I1 { void M(); }
interface I3 : I1 { void I1.M() { } }
interface I5 : I3
{
    void I1.M()
    {
        base.M(); // calls I3's implementation of I1.M
    }
}

Permit stackalloc in nested contexts(嵌套表达式中的 stackalloc)


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

Span<int> numbers = stackalloc[] { 1, 2, 3, 4, 5, 6 };
var ind = numbers.IndexOfAny(stackalloc[] { 2, 4, 6, 8 });
Console.WriteLine(ind);  // output: 1

Notnull constraint


 In C# 8.0, we added a language feature that permits the specification of a new type parameter constraint notnull. This is a placeholder for its specification.

null coalescing assignment(Null 合并赋值)


  C# 8.0 引入了 null 合并赋值运算符 ??=。 仅当左操作数计算为 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

Nullable Reference Types(可为空引用类型)


 C#8.0 引入了“可为空引用类型”和“不可为空引用类型”,使你能够对引用类型变量的属性作出重要声明 :

1.引用不应为 null。 当变量不应为 null 时,编译器会强制执行规则,以确保在不首先检查它们是否为 null 的情况下,取消引用这些变量是安全的:
  1.1必须将变量初始化为非 null 值。
  1.2变量永远不能赋值为 null。
2.引用可为 null。 当变量可以为 null 时,编译器会强制执行不同的规则以确保你已正确检查空引用:
  2.1只有当编译器可以保证该值不为 null 时,才可以取消引用该变量。
  2.2这些变量可以使用默认的 null 值进行初始化,也可以在其他代码中赋值为 null。
在 C# 的早期版本中,无法从变量声明中确定设计意图,与处理引用变量相比,这个新功能提供了显著的好处。 编译器不提供针对引用类型的空引用异常的安全性:
1.引用可为 null。 将引用类型初始化为 null 或稍后将其指定为 null 时,编译器不会发出警告。 在没有进行 null 检查的情况下取消引用这些变量,编译器会发出警告。
2.假定引用不为 null。 当引用类型被取消引用时,编译器不会发出任何警告。 如果将变量设置为可以为 null 的表达式,则编译器会发出警告。
将在编译时发出这些警告。 编译器不会在可为 null 的上下文中添加任何 null 检查或其他运行时构造。 在运行时,可为 null 的引用和不可为 null 的引用是等效的。
通过添加可为空引用类型,你可以更清楚地声明你的意图。 null 值是表示变量不引用值的正确方法。 请勿使用此功能从代码中删除所有 null 值。 相反,应该向编译器和其他读取代码的开发人员声明你的意图。 通过声明意图,编译器会在你编写与该意图不一致的代码时通知你。
使用与可为空值类型相同的语法记录可为空引用类型:将 ? 附加到变量的类型。 例如,以下变量声明表示可为空的字符串变量 name:

string? name;

未将 ? 附加到类型名称的任何变量都是“不可为 null 引用类型”。 这包括启用此功能时现有代码中的所有引用类型变量。

编译器使用静态分析来确定可为空引用是否为非 null。 如果你在一个可为空引用可能是 null 时对其取消引用,编译器将向你发出警告。 可以通过使用 NULL 包容运算符 ! 后跟变量名称来替代此行为。 例如,若知道 name 变量不为 null 但编译器仍发出警告,则可以编写以下代码来覆盖编译器的分析:

name!.Length;

类型为 Null 性

任何引用类型都可以具有四个“为 Null 性”中的一个,它描述了何时生成警告:

  • 不可为空:无法将 null 分配给此类型的变量。 在取消引用之前,无需对此类型的变量进行 null 检查。
  • 可为空:可将 null 分配给此类型的变量。 在不首先检查 null 的情况下取消引用此类型的变量时发出警告。
  • 无视:“无视”是 C# 8.0 之前版本的状态。 可以取消引用或分配此类型的变量而不发出警告。
  • 未知:“未知”通常针对类型参数,其中约束不告知编译器类型是否必须是“可为 null”或“不可为 null” 。

变量声明中类型的为 Null 性由声明变量的“可为空上下文”控制。

更详细请参考:https://docs.microsoft.com/zh-cn/dotnet/csharp/nullable-references

Obsolete on property accessor


 In C# 8.0, we added support for declaring a property accessor [Obsolete]. This is a placeholder for the specification.

Recursive Pattern Matching


The type pattern is useful for performing run-time type tests of reference types, and replaces the idiom(惯用语法) 

var v = expr as Type;
if (v != null) { // code using v

With the slightly more concise(简洁, 简明的)

if (expr is Type v) { // code using v

The type pattern can be used to test values of nullable types: a value of type Nullable<T> (or a boxed T) matches a type pattern T2 id if the value is non-null and the type of T2 is T, or some base type or interface of T. For example, in the code fragment

int? x = 3;
if (x is int v) { // code using v

The condition of the if statement is true at runtime and the variable v holds the value 3 of type int inside the block. After the block the variable v is in scope but not definitely assigned.

Constant Pattern

Var Pattern

Discard Pattern

Positional Pattern(位置模式)

某些类型包含 Deconstruct (解构)方法,该方法将其属性解构为离散变量。 如果可以访问 Deconstruct 方法,就可以使用位置模式检查对象的属性并将这些属性用于模式。 考虑以下 Point 类,其中包含用于为 X 和 Y 创建离散变量的 Deconstruct 方法:

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

此外,请考虑以下表示象限的各种位置的枚举:

public enum Quadrant
{
    Unknown,
    Origin,
    One,
    Two,
    Three,
    Four,
    OnBorder
}

下面的方法使用位置模式来提取 x 和 y 的值。 然后,它使用 when 子句来确定该点的 Quadrant

static Quadrant GetQuadrant(Point point) => point switch
{
    (0, 0) => Quadrant.Origin,
    var (x, y) when x > 0 && y > 0 => Quadrant.One,
    var (x, y) when x < 0 && y > 0 => Quadrant.Two,
    var (x, y) when x < 0 && y < 0 => Quadrant.Three,
    var (x, y) when x > 0 && y < 0 => Quadrant.Four,
    var (_, _) => Quadrant.OnBorder,
    _ => Quadrant.Unknown
};

当 x 或 y 为 0(但不是两者同时为 0)时,前一个开关中的弃元模式匹配。 Switch 表达式必须要么生成值,要么引发异常。 如果这些情况都不匹配,则 switch 表达式将引发异常。 如果没有在 switch 表达式中涵盖所有可能的情况,编译器将生成一个警告。

Property Pattern(属性模式)

借助属性模式,可以匹配所检查的对象的属性。 请看一个电子商务网站的示例,该网站必须根据买家地址计算销售税。 这种计算不是 Address 类的核心职责。 它会随时间变化,可能比地址格式的更改更频繁。 销售税的金额取决于地址的 State 属性。 下面的方法使用属性模式从地址和价格计算销售税:

public static decimal ComputeSalesTax(Address location, decimal salePrice) =>
    location switch
    {
        { State: "WA" } => salePrice * 0.06M,
        { State: "MN" } => salePrice * 0.075M,
        { State: "MI" } => salePrice * 0.05M,
        // other cases removed for brevity...
        _ => 0M
    };

Switch Expression(switch 表达式)

通常情况下,switch 语句在其每个 case 块中生成一个值。 借助 Switch 表达式,可以使用更简洁的表达式语法。 只有些许重复的 case 和 break 关键字和大括号。 以下面列出彩虹颜色的枚举为例:

public static RGBColor FromRainbow(Rainbow colorBand) =>
    colorBand switch
    {
        Rainbow.Red    => new RGBColor(0xFF, 0x00, 0x00),
        Rainbow.Orange => new RGBColor(0xFF, 0x7F, 0x00),
        Rainbow.Yellow => new RGBColor(0xFF, 0xFF, 0x00),
        Rainbow.Green  => new RGBColor(0x00, 0xFF, 0x00),
        Rainbow.Blue   => new RGBColor(0x00, 0x00, 0xFF),
        Rainbow.Indigo => new RGBColor(0x4B, 0x00, 0x82),
        Rainbow.Violet => new RGBColor(0x94, 0x00, 0xD3),
        _              => throw new ArgumentException(message: "invalid enum value", paramName: nameof(colorBand)),
    };

这里有几个语法改进:

  • 变量位于 switch 关键字之前。 不同的顺序使得在视觉上可以很轻松地区分 switch 表达式和 switch 语句。
  • 将 case 和 : 元素替换为 =>。 它更简洁,更直观。
  • 将 default 事例替换为 _ 弃元。
  • 正文是表达式,不是语句。

将其与使用经典 switch 语句的等效代码进行对比:

public static RGBColor FromRainbowClassic(Rainbow colorBand)
{
    switch (colorBand)
    {
        case Rainbow.Red:
            return new RGBColor(0xFF, 0x00, 0x00);
        case Rainbow.Orange:
            return new RGBColor(0xFF, 0x7F, 0x00);
        case Rainbow.Yellow:
            return new RGBColor(0xFF, 0xFF, 0x00);
        case Rainbow.Green:
            return new RGBColor(0x00, 0xFF, 0x00);
        case Rainbow.Blue:
            return new RGBColor(0x00, 0x00, 0xFF);
        case Rainbow.Indigo:
            return new RGBColor(0x4B, 0x00, 0x82);
        case Rainbow.Violet:
            return new RGBColor(0x94, 0x00, 0xD3);
        default:
            throw new ArgumentException(message: "invalid enum value", paramName: nameof(colorBand));
    };
}

Ranges(索引和范围)


 索引和范围为访问序列中的单个元素或范围提供了简洁的语法。

此语言支持依赖于两个新类型和两个新运算符:

  • System.Index 表示一个序列索引。
  • 来自末尾运算符 ^ 的索引,指定一个索引与序列末尾相关。
  • System.Range 表示序列的子范围。
  • 范围运算符 ..,用于指定范围的开始和末尾,就像操作数一样。

让我们从索引规则开始。 请考虑数组 sequence。 0 索引与 sequence[0] 相同。 ^0 索引与 sequence[sequence.Length] 相同。 请注意,sequence[^0] 不会引发异常,就像 sequence[sequence.Length] 一样。 对于任何数字 n,索引 ^n 与 sequence.Length - n 相同。

范围指定范围的开始和末尾 。 包括此范围的开始,但不包括此范围的末尾,这表示此范围包含开始但不包含末尾 。 范围 [0..^0] 表示整个范围,就像 [0..sequence.Length] 表示整个范围。

请看以下几个示例。 请考虑以下数组,用其顺数索引和倒数索引进行注释:

var words = new string[]
{
                // index from start    index from end
    "The",      // 0                   ^9
    "quick",    // 1                   ^8
    "brown",    // 2                   ^7
    "fox",      // 3                   ^6
    "jumped",   // 4                   ^5
    "over",     // 5                   ^4
    "the",      // 6                   ^3
    "lazy",     // 7                   ^2
    "dog"       // 8                   ^1
};              // 9 (or words.Length) ^0
Console.WriteLine($"The last word is {words[^1]}");
// writes "dog"

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

Range phrase = 1..4;
var text = words[phrase];

Readonly Instance Members(Readonly 成员)


 可将 readonly 修饰符应用于结构的成员。 它指示该成员不会修改状态。 这比将 readonly 修饰符应用于 struct 声明更精细。 请考虑以下可变结构:

public readonly double Distance => Math.Sqrt(X * X + Y * Y);

请注意,readonly 修饰符对于只读属性是必需的。 编译器会假设 get 访问器可以修改状态;必须显式声明 readonly 自动实现的属性是一个例外;编译器会将所有自动实现的 Getter 视为 readonly,因此,此处无需向 X 和 Y 属性添加 readonly 修饰符。

编译器确实会强制执行 readonly 成员不修改状态的规则。 除非删除 readonly 修饰符,否则不会编译以下方法:

public readonly void Translate(int xOffset, int yOffset)
{
    X += xOffset;
    Y += yOffset;
}

Name shadowing in nested functions


In C# 8.0, we added a feature that permits parameters and locals in lambdas and local functions to use names that hide/shadow the names of locals or parameters from the enclosing scope. This is a placholder for its specification.

Static local functions(静态本地函数)


现在可以向本地函数添加 static 修饰符,以确保本地函数不会从封闭范围捕获(引用)任何变量。 这样做会生成 CS8421,“静态本地函数不能包含对 <variable> 的引用。”

考虑下列代码。 本地函数 LocalFunction 访问在封闭范围(方法 M)中声明的变量 y。 因此,不能用 static 修饰符来声明 LocalFunction

static int WriteLinesToFile(IEnumerable<string> lines)
{
    // We must declare the variable outside of the using block
    // so that it is in scope to be returned.
    int skippedLines = 0;
    using (var file = new System.IO.StreamWriter("WriteLines2.txt"))
    {
        foreach (string line in lines)
        {
            if (!line.Contains("Second"))
            {
                file.WriteLine(line);
            }
            else
            {
                skippedLines++;
            }
        }
        return skippedLines;
    } // file is disposed here
}

在前面的示例中,当到达与 using 语句关联的右括号时,将对该文件进行处理。

在这两种情况下,编译器将生成对 Dispose() 的调用。 如果 using 语句中的表达式不可用,编译器将生成一个错误。

 

原文地址:https://github.com/dotnet/csharplang/tree/master/proposals/csharp-8.0

原文地址: https://docs.microsoft.com/zh-cn/dotnet/csharp/whats-new/csharp-8

 

posted @ 2020-10-16 16:42  FH1004322  阅读(266)  评论(0)    收藏  举报