C# 8.0 添加和增强的功能【基础篇】

.NET Core 3.x.NET Standard 2.1支持 C# 8.0

一、Readonly 成员

可将 readonly 修饰符应用于结构的成员,来限制成员为不可修改状态。这比在 C# 7.2中将 readonly 修饰符仅可应用于 struct 声明更精细。

public struct Point
{
    public double X { get; set; }
    public double Y { get; set; }
    public double Distance => Math.Sqrt(X * X + Y * Y);
    public override string ToString() =>
        $"({X}, {Y}) is {Distance} from the origin";
}

与大多数结构一样, ToString() 方法不会修改状态。 可以通过将 readonly 修饰符添加到 ToString() 的声明来对此进行限制:

public readonly override string ToString() =>// 编译器警告,因为 ToString 访问未标记为 readonly 的 Distance 属性
    $"({X}, {Y}) is {Distance} from the origin";
// Distance 属性不会更改状态,因此可以通过将 readonly 修饰符添加到声明来修复此警告
public readonly double Distance => Math.Sqrt(X * X + Y * Y);

注意: readonly 修饰符对于只读属性是必需的。

编译器会假设 get 访问器可以修改状态;必须显式声明 readonly

自动实现的属性是一个例外;编译器会 将所有自动实现的 Getter 视为 readonly,因此,此处无需向 XY 属性添加 readonly 修饰符。

通过此功能,可以指定设计意图,使编译器可以强制执行该意图,并基于该意图进行优化。

二、默认接口方法

.NET Core 3.0 上的 C# 8.0 开始, 可以在声明接口成员时定义实现。 最常见的方案是,可以将成员添加到已经由无数客户端发布并使用的接口。示例:

// 先声明两个接口
// 客户接口
public interface ICustomer
{
    IEnumerable<iorder> PreviousOrders { get; }
    DateTime DateJoined { get; }
    DateTime? LastOrder { get; }
    string Name { get; }
    IDictionary<datetime, string> Reminders { get; }

    // &#x5728;&#x5BA2;&#x6237;&#x63A5;&#x53E3;&#x4E2D;&#x52A0;&#x5165;&#x65B0;&#x7684;&#x65B9;&#x6CD5;&#x5B9E;&#x73B0;
    public decimal ComputeLoyaltyDiscount()
    {
        DateTime TwoYearsAgo = DateTime.Now.AddYears(-2);
        if ((DateJoined < TwoYearsAgo) && (PreviousOrders.Count() > 10))
        {
            return 0.10m;
        }
        return 0;
    }
}
// &#x8BA2;&#x5355;&#x63A5;&#x53E3;
public interface IOrder
{
    DateTime Purchased { get; }
    decimal Cost { get; }
}

// &#x6D4B;&#x8BD5;&#x4EE3;&#x7801;
// SampleCustomer&#xFF1A;&#x63A5;&#x53E3; ICustomer &#x7684;&#x5B9E;&#x73B0;&#xFF0C;&#x53EF;&#x4E0D;&#x5B9E;&#x73B0;&#x65B9;&#x6CD5; ComputeLoyaltyDiscount
// SampleOrder&#xFF1A;&#x63A5;&#x53E3; IOrder &#x7684;&#x5B9E;&#x73B0;
SampleCustomer c = new SampleCustomer("customer one", new DateTime(2010, 5, 31))
{
    Reminders =
    {
        { new DateTime(2010, 08, 12), "childs's birthday" },
        { new DateTime(1012, 11, 15), "anniversary" }
    }
};

SampleOrder o = new SampleOrder(new DateTime(2012, 6, 1), 5m);
c.AddOrder(o);//&#x6DFB;&#x52A0;&#x8BA2;&#x5355;
o = new SampleOrder(new DateTime(2103, 7, 4), 25m);
c.AddOrder(o);

// &#x9A8C;&#x8BC1;&#x65B0;&#x589E;&#x7684;&#x63A5;&#x53E3;&#x65B9;&#x6CD5;
ICustomer theCustomer = c; // &#x4ECE; SampleCustomer &#x5230; ICustomer &#x7684;&#x5F3A;&#x5236;&#x8F6C;&#x6362;
Console.WriteLine($"Current discount: {theCustomer.ComputeLoyaltyDiscount()}");
// &#x82E5;&#x8981;&#x8C03;&#x7528;&#x5728;&#x63A5;&#x53E3;&#x4E2D;&#x58F0;&#x660E;&#x548C;&#x5B9E;&#x73B0;&#x7684;&#x4EFB;&#x4F55;&#x65B9;&#x6CD5;&#xFF0C;&#x8BE5;&#x53D8;&#x91CF;&#x7684;&#x7C7B;&#x578B;&#x5FC5;&#x987B;&#x662F;&#x63A5;&#x53E3;&#x7C7B;&#x578B;&#xFF0C;&#x5373;&#xFF1A;theCustomer</datetime,></iorder>

另可参考大佬文章: C# 8.0 的默认接口方法

三、模式匹配的增强功能

C# 8.0扩展了 C# 7.0中的词汇表( isswitch),这样就可以在代码中的更多位置使用更多模式表达式。

3.1 switch 表达式

区别与 switch 语句:

变量位于 switch 关键字之前;

将 case 和 : 元素替换为 =>,更简洁、直观;

将 default 事例替换为 _ 弃元;

实际语句是表达式,比语句更加简洁。

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

3.2 属性模式

借助属性模式,可以匹配所检查的对象的属性。

如下电子商务网站的示例,该网站必须根据买家地址(Address 对象的 State 属性)计算销售税。

// Address&#xFF1A;&#x5730;&#x5740;&#x5BF9;&#x8C61;&#xFF1B;salePrice&#xFF1A;&#x552E;&#x4EF7;
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
    };

此写法,使得整个语法更为简洁。

3.3 元组模式

日常开发中,存在算法依赖于多个输入。使用元组模式,可根据 表示为元组 的多个值进行切换。

// &#x6E38;&#x620F;&#x201C;rock, paper, scissors&#xFF08;&#x77F3;&#x5934;&#x526A;&#x5200;&#x5E03;&#xFF09;&#x201D;&#x7684;&#x5207;&#x6362;&#x8868;&#x8FBE;&#x5F0F;
public static string RockPaperScissors(string first, string second)
    => (first, second) switch
    {
        ("rock", "paper") => "rock is covered by paper. Paper wins.",
        ("rock", "scissors") => "rock breaks scissors. Rock wins.",
        ("paper", "rock") => "paper covers rock. Paper wins.",
        ("paper", "scissors") => "paper is cut by scissors. Scissors wins.",
        ("scissors", "rock") => "scissors is broken by rock. Rock wins.",
        ("scissors", "paper") => "scissors cuts paper. Scissors wins.",
        (_, _) => "tie" // &#x6B64;&#x5904;&#x5F03;&#x5143; &#x8868;&#x793A;&#x5E73;&#x5C40;(&#x77F3;&#x5934;&#x526A;&#x5200;&#x5E03;&#x6E38;&#x620F;)&#x7684;&#x4E09;&#x79CD;&#x7EC4;&#x5408;&#x6216;&#x5176;&#x4ED6;&#x6587;&#x672C;&#x8F93;&#x5165;
    };

3.4 位置模式

某些类型包含 Deconstruct 方法,该方法将其属性解构为离散变量。 如果可以访问 Deconstruct 方法,就可以使用位置模式检查对象的属性并将这些属性用于模式。

// &#x4F4D;&#x4E8E;&#x8C61;&#x9650;&#x4E2D;&#x7684; &#x70B9;&#x5BF9;&#x8C61;
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// &#x8C61;&#x9650;
{
    Unknown, Origin, One, Two, Three, Four, OnBorder
}
// &#x4E0B;&#x9762;&#x7684;&#x65B9;&#x6CD5;&#x4F7F;&#x7528;&#x4F4D;&#x7F6E;&#x6A21;&#x5F0F;&#x6765;&#x63D0;&#x53D6; x &#x548C; y &#x7684;&#x503C;&#x3002; &#x7136;&#x540E;&#xFF0C;&#x5B83;&#x4F7F;&#x7528; when &#x5B50;&#x53E5;&#x6765;&#x786E;&#x5B9A;&#x8BE5;&#x70B9;&#x7684; 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,// &#x5F53; x &#x6216; y &#x4E3A; 0&#xFF08;&#x4F46;&#x4E0D;&#x662F;&#x4E24;&#x8005;&#x540C;&#x65F6;&#x4E3A; 0&#xFF09;&#x65F6;&#xFF0C;&#x524D;&#x4E00;&#x4E2A;&#x5F00;&#x5173;&#x4E2D;&#x7684;&#x5F03;&#x5143;&#x6A21;&#x5F0F;&#x5339;&#x914D;
    _ => Quadrant.Unknown
};

如果没有在 switch 表达式中涵盖所有可能的情况,编译器将生成一个警告。

四、using 声明

using 声明是前面带 using 关键字的变量声明。它指示编译器声明的变量应在封闭范围的末尾进行处理。

static int WriteLinesToFile(IEnumerable<string> lines)
{
    using var file = new System.IO.StreamWriter("WriteLines2.txt");
    int skippedLines = 0;
    foreach (string line in lines)
    {
        if (!line.Contains("Second"))
            file.WriteLine(line);
        else
            skippedLines++;
    }
    return skippedLines;
    // &#x5F53;&#x4EE3;&#x7801;&#x8FD0;&#x884C;&#x5230;&#x6B64;&#x4F4D;&#x7F6E;&#x65F6;&#xFF0C;file &#x88AB;&#x9500;&#x6BC1;
    // &#x76F8;&#x5F53;&#x4E8E; using (var file = new System.IO.StreamWriter("WriteLines2.txt")){ ... }
}</string>

如果 using 语句中的表达式不可用,编译器将生成一个错误。

五、静态本地函数

C# 8.0中可以向本地函数添加 static 修饰符,以确保本地函数不会从封闭范围捕获(引用)任何变量。 若引用了就会生成报错: CS8421-“静态本地函数不能包含对

// &#x672C;&#x5730;&#x65B9;&#x6CD5; LocalFunction &#x8BBF;&#x95EE;&#x4E86;&#x65B9;&#x6CD5; M() &#x8FD9;&#x4E2A;&#x5C01;&#x95ED;&#x7A7A;&#x95F4;&#x7684;&#x53D8;&#x91CF; y
// &#x56E0;&#x6B64;&#xFF0C;&#x4E0D;&#x80FD;&#x7528; static &#x4FEE;&#x9970;&#x7B26;&#x6765;&#x58F0;&#x660E;
int M()
{
    int y;
    LocalFunction();
    return y;
    void LocalFunction() => y = 0;
}
// Add &#x65B9;&#x6CD5;&#x53EF;&#x4EE5;&#x662F;&#x9759;&#x6001;&#x7684;&#xFF0C;&#x56E0;&#x4E3A;&#x5B83;&#x4E0D;&#x8BBF;&#x95EE;&#x5C01;&#x95ED;&#x8303;&#x56F4;&#x5185;&#x7684;&#x4EFB;&#x4F55;&#x53D8;&#x91CF;
int M()
{
    int y = 5;
    int x = 7;
    return Add(x, y);
    static int Add(int left, int right) => left + right;
}

六、可处置的 ref 结构

ref 修饰符声明的 struct 可能无法实现任何接口,也包括接口 IDisposable

class Program
{
   static void Main(string[] args)
   {
      using (var book = new Book())
         Console.WriteLine("Hello World!");
   }
}
// &#x9519;&#x8BEF;&#x5199;&#x6CD5;
// Error CS8343 'Book': ref structs cannot implement interfaces
ref struct Book : IDisposable
{
   public void Dispose()
   {   }
}
// &#x6B63;&#x786E;&#x5199;&#x6CD5;
class Program
{
   static void Main(string[] args)
   {
      // &#x6839;&#x636E; using &#x65B0;&#x7279;&#x6027;&#xFF0C;&#x7B80;&#x6D01;&#x7684;&#x5199;&#x6CD5;&#xFF0C;&#x9ED8;&#x8BA4;&#x5728;&#x5F53;&#x524D;&#x4EE3;&#x7801;&#x5757;&#x7ED3;&#x675F;&#x524D;&#x9500;&#x6BC1;&#x5BF9;&#x8C61; book
      using var book = new Book();
      // ...

    }
}
ref struct Book
{
   public void Dispose()
   {
   }
}

因此,若要能够处理 ref struct,就必须有一个可访问的 void Dispose() 方法。

此功能同样适用于 readonly ref struct 声明。

七、可为空引用类型

若要指示一个变量 可能为 null,必须在类型名称 后面附加 ?,以将该变量声明为可为空引用类型。否则都被视为不可为空引用类型。

对于不可为空引用类型,编译器使用流分析来确保在声明时将本地变量初始化为非 Null 值。 字段必须在 构造过程中初始化。 如果没有通过调用任何可用的构造函数或通过初始化表达式来设置变量,编译器将生成警告。

此外,不能向不可为空引用类型分配一个可以为 Null 的值。

编译器使用流分析,来确保可为空引用类型的任何变量,在被访问或分配给不可为空引用类型之前,都会对其 Null 性进行检查。

string? n = "world";
var x = b ? "Hello" : n; // string?

var y = b ? "Hello" : null; // string? or error
var z = b ? 7 : null; // Error today, could be int?

八、异步流

异步流,可针对流式处理数据源建模 。 数据流经常异步检索或生成元素,因此它们为异步流式处理数据源提供了自然编程模型。

// &#x5F02;&#x6B65;&#x679A;&#x4E3E;&#xFF0C;&#x6838;&#x5FC3;&#x5BF9;&#x8C61;&#x662F;&#xFF1A;IAsyncEnumerable
[HttpGet("syncsale")]
public async IAsyncEnumerable<product> GetOnSaleProducts()
{
    var products = _repository.GetProducts();
    await foreach (var product in products) // &#x6D88;&#x8D39;&#x5F02;&#x6B65;&#x679A;&#x4E3E;&#xFF0C;&#x987A;&#x5E8F;&#x53D6;&#x51B3;&#x4E8E; IAsyncEnumerator &#x7B97;&#x6CD5;
    {
        if (product.IsOnSale)
            yield return product;// &#x6301;&#x7EED;&#x5F02;&#x6B65;&#x9010;&#x4E2A;&#x8FD4;&#x56DE;&#xFF0C;&#x4E0D;&#x7528;&#x7B49;&#x5168;&#x90E8;&#x5B8C;&#x6210;
    }
}</product>

另一个实例:模拟异步抓取 html 数据

// &#x8FD9;&#x662F;&#x4E00;&#x4E2A;&#x3010;&#x76F8;&#x4E92;&#x72EC;&#x7ACB;&#x7684;&#x957F;&#x8017;&#x65F6;&#x884C;&#x4E3A;&#x7684;&#x96C6;&#x5408;&#xFF08;&#x5047;&#x8BBE;&#x5206;&#x522B;&#x8017;&#x65F6; 5,4,3,2,1s&#xFF09;&#x3011;
static async Task Main(string[] args)
{
      Console.WriteLine(DateTime.Now + $"\tThreadId:{Thread.CurrentThread.ManagedThreadId}\r\n");
      await foreach (var html in FetchAllHtml()) // &#x9ED8;&#x8BA4;&#x6309;&#x7167;&#x4EFB;&#x52A1;&#x52A0;&#x5165;&#x7684;&#x987A;&#x5E8F;&#x8F93;&#x51FA;
      {
           Console.WriteLine(DateTime.Now + $"\tThreadId:{Thread.CurrentThread.ManagedThreadId}\t" + $"\toutput:{html}");
      }
      Console.WriteLine("\r\n" + DateTime.Now + $"\tThreadId:{Thread.CurrentThread.ManagedThreadId}\t");
      Console.ReadKey();
 }
 // &#x8FD9;&#x91CC;&#x5DF2;&#x7ECF;&#x9ED8;&#x8BA4;&#x5B9E;&#x73B0;&#x4E86;&#x4E00;&#x4E2A; IEnumerator &#x679A;&#x4E3E;&#x5668;&#xFF1A; &#x4EE5; for &#x5FAA;&#x73AF;&#x52A0;&#x5165;&#x5F02;&#x6B65;&#x4EFB;&#x52A1;&#x7684;&#x987A;&#x5E8F;
 static async IAsyncEnumerable<string> FetchAllHtml()
 {
    for (int i = 5; i >= 1; i--)
    {
        var html = await Task.Delay(i* 1000).ContinueWith((t,i)=> $"html{i}",i); // &#x6A21;&#x62DF;&#x957F;&#x8017;&#x65F6;
        yield return html;
    }
 }</string>

C# 8.0 添加和增强的功能【基础篇】

接着,其实五个操作是分别开始执行的,那么当耗时短的任务处理好后,能否直接输出呢?这样的话交互体验就更好了!

static async IAsyncEnumerable<string> FetchAllHtml()
{
    var tasklist= new List<task<string>>();
    for (int i = 5; i >= 1; i--)
    {
       var t= Task.Delay(i* 1000).ContinueWith((t,i)=>$"html{i}",i);// &#x6A21;&#x62DF;&#x957F;&#x8017;&#x65F6;&#x4EFB;&#x52A1;
       tasklist.Add(t);
    }
    while(tasklist.Any())  // &#x76D1;&#x63A7;&#x5DF2;&#x5B8C;&#x6210;&#x7684;&#x64CD;&#x4F5C;&#xFF0C;&#x7ACB;&#x5373;&#x5904;&#x7406;
    {
      var tFinlish = await Task.WhenAny(tasklist);
      tasklist.Remove(tFinlish);
      yield return await tFinlish; // &#x5B8C;&#x6210;&#x5373;&#x8F93;&#x51FA;
    }
}  </task<string></string>

以上总耗时取决于 耗时最长的那个异步任务5s。

C# 8.0 添加和增强的功能【基础篇】

参考自: C# 8.0 宝藏好物 Async streams

九、异步可释放(IAsyncDisposable)

IAsyncDisposable接口,提供一种用于异步释放非托管资源的机制。与之对应的就是提供同步释放非托管资源机制的接口 IDisposable

提供此类及时释放机制,可使用户执行资源密集型释放操作,从而无需长时间占用 GUI 应用程序的主线程。

同时更好的完善 .NET异步编程的体验, IAsyncDisposable诞生了。它的用法与 IDisposable非常的类似:

public class ExampleClass : IAsyncDisposable
{
    private Stream _memoryStream = new MemoryStream();
    public ExampleClass()
    {   }
    public async ValueTask DisposeAsync()
    {
        await _memoryStream.DisposeAsync();
    }
}
// using &#x8BED;&#x6CD5;&#x7CD6;
await using var s = new ExampleClass()
{
    // doing
};
// &#x4F18;&#x5316; &#x540C;&#x6837;&#x662F;&#x5BF9;&#x8C61; s &#x53EA;&#x5B58;&#x5728;&#x4E8E;&#x5F53;&#x524D;&#x4EE3;&#x7801;&#x5757;
await using var s = new ExampleClass();
// doing

参考于:熟悉而陌生的新朋友——IAsyncDisposable

十、索引和范围

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

新增了两个类型( System.Index & System.Range)和运算符(末尾运算符” ^” & 范围运算符” ..“)。

用例子说话吧:

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]}");
// &#x201C;dog&#x201D; // &#x4F7F;&#x7528; ^1 &#x7D22;&#x5F15;&#x68C0;&#x7D22;&#x6700;&#x540E;&#x4E00;&#x4E2A;&#x8BCD;

var quickBrownFox = words[1..4];
//&#x201C;quick&#x201D;&#x3001;&#x201C;brown&#x201D;&#x3001;&#x201C;fox&#x201D; &#x5B50;&#x8303;&#x56F4;

var lazyDog = words[^2..^0];
// &#x201C;lazy&#x201D;&#x3001;&#x201C;dog&#x201D; &#x5B50;&#x8303;&#x56F4;

var allWords = words[..];
// &#x201C;The&#x201D;&#x3001;&#x201C;dog&#x201D;&#x5B50;&#x8303;&#x56F4;
var firstPhrase = words[..4];
// &#x201C;The&#x201D;&#x3001;&#x201C;fox&#x201D;&#x5B50;&#x8303;&#x56F4;
var lastPhrase = words[6..];
// &#x201C;the&#x201D;&#x3001;&#x201C;lazy&#x201D;&#x3001;&#x201C;dog&#x201D;&#x5B50;&#x8303;&#x56F4;

另外可将范围声明为变量:

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

十一、 Null 合并赋值

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</int></int>

十二、非托管构造类型

在 C# 7.3 及更低版本中,构造类型(包含至少一个类型参数的类型)不能为非托管类型。 从 C# 8.0 开始,如果构造的值类型 仅包含非托管类型的字段,则该类型不受管理。

public struct Coords<t>
{
    public T X;
    public T Y;
}
// Coords<int> &#x7C7B;&#x578B;&#x4E3A; C# 8.0 &#x53CA;&#x66F4;&#x9AD8;&#x7248;&#x672C;&#x4E2D;&#x7684;&#x975E;&#x6258;&#x7BA1;&#x7C7B;&#x578B;
// &#x4E0E;&#x4EFB;&#x4F55;&#x975E;&#x6258;&#x7BA1;&#x7C7B;&#x578B;&#x4E00;&#x6837;&#xFF0C;&#x53EF;&#x4EE5;&#x521B;&#x5EFA;&#x6307;&#x5411;&#x6B64;&#x7C7B;&#x578B;&#x7684;&#x53D8;&#x91CF;&#x7684;&#x6307;&#x9488;&#xFF0C;&#x6216;&#x9488;&#x5BF9;&#x6B64;&#x7C7B;&#x578B;&#x7684;&#x5B9E;&#x4F8B;&#x5728;&#x5806;&#x6808;&#x4E0A;&#x5206;&#x914D;&#x5185;&#x5B58;&#x5757;
Span<coords<int>> coordinates = stackalloc[]
{
    new Coords<int> { X = 0, Y = 0 },
    new Coords<int> { X = 0, Y = 3 },
    new Coords<int> { X = 4, Y = 0 }
};</int></int></int></coords<int></int></t>

Span 简介

在定义中,Span 就是一个简单的 值类型。它真正的价值,在于允许我们与 任何类型的连续内存一起工作。

在使用中,Span 确保了内存和数据安全,而且几乎没有开销。

要使用 Span,需要设置开发语言为 C# 7.2 以上,并引用 System.Memory到项目。

Span 使用时,最简单的,可以把它 想象成一个数组,有一个 Length属性和一个允许读写的 index

// &#x5E38;&#x7528;&#x7684;&#x4E00;&#x4E9B;&#x5B9A;&#x4E49;&#x3001;&#x5C5E;&#x6027;&#x548C;&#x65B9;&#x6CD5;
Span(T[]&#xA0;array);
Span(T[]&#xA0;array,&#xA0;int&#xA0;startIndex);
Span(T[]&#xA0;array,&#xA0;int&#xA0;startIndex,&#xA0;int&#xA0;length);
unsafe&#xA0;Span(void*&#xA0;memory,&#xA0;int&#xA0;length);
int&#xA0;Length&#xA0;{&#xA0;get;&#xA0;}
ref&#xA0;T&#xA0;this[int&#xA0;index]&#xA0;{&#xA0;get;&#xA0;set;&#xA0;}
Span<t>&#xA0;Slice(int&#xA0;start);
Span<t>&#xA0;Slice(int&#xA0;start,&#xA0;int&#xA0;length);
void&#xA0;Clear();
void&#xA0;Fill(T&#xA0;value);
void&#xA0;CopyTo(Span<t>&#xA0;destination);
bool&#xA0;TryCopyTo(Span<t>&#xA0;destination);
// &#x4ECE; T[] &#x5230; Span &#x7684;&#x9690;&#x5F0F;&#x8F6C;&#x6362;
char[]&#xA0;array&#xA0;=&#xA0;new&#xA0;char[]&#xA0;{&#xA0;'i',&#xA0;'m',&#xA0;'p',&#xA0;'l',&#xA0;'i',&#xA0;'c',&#xA0;'i',&#xA0;'t'&#xA0;};
Span<char>&#xA0;fromArray&#xA0;=&#xA0;array;
// &#x590D;&#x5236;&#x5185;&#x5B58;
int&#xA0;Parse(ReadOnlySpan<char>&#xA0;anyMemory);
int&#xA0;Copy<t>(ReadOnlySpan<t>&#xA0;source,&#xA0;Span<t>&#xA0;destination);</t></t></t></char></char></t></t></t></t>

Span 参考:关于C# Span的一些实践

十三、嵌套表达式中的 stackalloc

从 C# 8.0 开始,如果 stackalloc 表达式的结果为 System.Span

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

stackalloc 表达式简介:

stackalloc 关键字用于不安全的代码上下文中,以便在堆栈上分配内存块。

// &#x5173;&#x952E;&#x5B57;&#x4EC5;&#x5728;&#x5C40;&#x90E8;&#x53D8;&#x91CF;&#x7684;&#x521D;&#x59CB;&#x503C;&#x4E2D;&#x6709;&#x6548;&#xFF0C;&#x6B63;&#x786E;&#x5199;&#x6CD5;&#xFF1A;
int* block = stackalloc int[100];
// &#x9519;&#x8BEF;&#x5199;&#x6CD5;&#xFF1A;
int* block;
block = stackalloc int[100];

由于涉及指针类型,因此 stackalloc 要求不安全上下文。

以下代码示例计算并演示 Fibonacci 序列中的前 20 个数字。 每个数字是先前两个数字的和。 在代码中,大小足够容纳 20 个 int 类型元素的内存块是在堆栈上分配的,而不是在堆上分配的。
该块的地址存储在 fib 指针中。 此内存不受垃圾回收的制约,因此不必将其钉住(通过使用 fixed)。 内存块的生存期受限于定义它的方法的生存期。 不能在方法返回之前释放内存。

class Test
{
    static unsafe void Main()
    {
        const int arraySize = 20;
        int* fib = stackalloc int[arraySize];
        int* p = fib;
        *p++ = *p++ = 1;// The sequence begins with 1, 1.

        for (int i = 2; i < arraySize; ++i, ++p)
            *p = p[-1] + p[-2];// Sum the previous two numbers.

        for (int i = 0; i < arraySize; ++i)
            Console.WriteLine(fib[i]);
        // Keep the console window open in debug mode.

        System.Console.WriteLine("Press any key to exit.");
        System.Console.ReadKey();
    }
}
/*
Output
1
1
2
3
5
8
13
21
34
55
89
144
233
377
610
987
1597
2584
4181
6765
*/

不安全代码的安全性低于安全替代代码。 但是,通过使用 stackalloc 可以自动启用公共语言运行时 (CLR) 中的缓冲区溢出检测功能。 如果检测到缓冲区溢出,进程将尽快终止,以最大限度地减小执行恶意代码的机会。

stackalloc 表达式参考:C#不安全代码和stackalloc

十四、内插逐字字符串的增强功能

内插逐字字符串中 $ 和 @ 标记的顺序可以任意安排:$@”…” 和 @$”…” 均为有效的内插逐字字符串。

在早期 C# 版本中,$ 标记必须出现在 @ 标记之前。

注:暂时整理这些,欢迎指正和补充。

Original: https://www.cnblogs.com/czzj/p/16832990.html
Author: 橙子家
Title: C# 8.0 添加和增强的功能【基础篇】

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/799744/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球