美文网首页
盘点C# 8.0中好用的特性

盘点C# 8.0中好用的特性

作者: 程序员ken | 来源:发表于2023-04-08 17:30 被阅读0次

    增加引用类型可为null:

    //如果参数t是null,则会发出警告
    public static void Test<T?>(T t){            
    }
    

    模式匹配

    1、swith 多条件匹配

    ## 1 元组模式 
    int a = 1;
    int b = 10;
     string c = (a, b) switch {
     (1, 10) =>  "123",
      _ =>  "default",
    };
    
    ### 使用弃元模式
    _ = (a, b) switch {
        (1, 10) =>  "123",
         _ =>  "default",
    };
    
    ## 2 位置模式
        public class PointA
        {
            public int X { get; }
            
            public int Y { get; }
            public int Z { get; }
            public PointA(int x, int y) => (X, Y) = (x, y);
            public PointA(int x, int y,int z) => (X, Y,Z) = (x, y,z);
            public void Deconstruct(out int x, out int y) => (x, y) = (X, Y);
            public void Deconstruct(out int x, out int y,out int z) => (x, y,z) = (X, Y,Z);
        }
    
    static string GetLocation(PointA point) => point switch
            {
                (0, 0) => "1",
                (0, 0, 0) => "1",
                var (x, y) when x > 0 && y > 0 =>"2",
                var (_, _) => "3",// 当 x 或 y 不为null时
                _ => "4"
       };
    

    Using 声明

    ## 先熟悉下使用 此处 C#7就能用了
    
    using static System.Math;
    using Tasks = System.Threading.Tasks;
    
    using (FileStream fileStream = new FileStream("", FileMode.Open))  {  }
    
    ##  C#8
    using FileStream fileStream2 = new FileStream("", FileMode.Open);
    

    接口特性增加

    --- 可以声明一个属性
    --- 方法可以实现(不再是只定义)

      public interface MyInterface
        {
            public int Age { get; set; }
            void Method1(int age)
            {
                Console.WriteLine(age);
            }
        }
    

    Readonly 成员

    public readonly struct Coords
      {
          public Coords(double x, double y)
            {
                X = x;
                Y = y;
            }
    
          public double X { get; set; }
          public double Y { get; set; }
    
          public override string ToString() => $"({X}, {Y})";
    
      }
    

    ---- 结构体 局部只读

    public struct Coords{
    
          public double X { get; set; }
          public double Y { get; set; }
    
          private int counter;
          public  int Counter
            {
                readonly get => counter;
                set => counter = value;
            }
    
         /*public Coords(double x, double y, int _counter)
            {
                X = x;
                Y = y;
                counter = _counter;
            }*/
    
          public override string ToString() => $"({X}, {Y})";
    
          public readonly double Sum()
            {
                /*X = 200;
                Y = 200;*/
                return X + Y;
            }
    
    
      }
    

    静态本地函数

    总结

    支持禁止从封闭范围捕获状态的本地函数。

    详细设计

    声明的本地函数 static 无法从封闭范围中捕获状态。 因此, this 局部函数中不提供封闭范围内的局部变量、参数和 static 。
    static局部函数不能通过隐式或显式或引用来引用实例成员 this base 。
    static局部函数可以引用 static 封闭范围内的成员。

    static int z = 0;
    
     int InnerMethod(){
        int y = 5;
        int x = 7;
           
        return LocalStaticFunction(x, y);
        //静态本地函数
        static int LocalStaticFunction(int left, int right) {
                    return z + left + right;
        }
    }
    

    异步流

    C#8.0之前 支持迭代器方法和异步方法,但不支持同时作为迭代器和异步的方法。 我们应该通过允许 await 使用新的迭代器形式来纠正这种情况 async ,它将返回 IAsyncEnumerable<T> 或 IAsyncEnumerator<T> 而不是 IEnumerable<T> 或 IEnumerator<T> , IAsyncEnumerable<T> 在新的中使用 await foreach 。 IAsyncDisposable接口还用于启用异步清理。

    新增接口: IAsyncDisposable 、IAsyncEnumerable/IAsyncEnumerator

    通常情况下,任何时候清理资源所需的时间可能会很有用,例如关闭文件 (需要刷新) ,取消注册回调并提供一种方法来了解注销完成的时间等。

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

    与一样 Dispose , DisposeAsync 多次调用都是可接受的,并且第一次调用之后的调用应被视为 nops(意味着后续调用都应该是 nop),返回同步完成的任务 (DisposeAsync 不需要线程安全的,并且无需支持) 的并发调用。
    名称解释:nop不执行任何操作

    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; }
        }
    }
    
    IAsyncEnumerator<T> enumerator = enumerable.GetAsyncEnumerator();
    try
    {
        while (await enumerator.MoveNextAsync())
        {
            Use(enumerator.Current);
        }
    }
    finally { await enumerator.DisposeAsync(); }
    
    static async void Main(string[] args) {
        IAsyncEnumerable<string> asyncEnumerable = AsyncEnumerableTest();
        IAsyncEnumerator<string> asyncEnumerator = asyncEnumerable.GetAsyncEnumerator();
        try{
           while (await asyncEnumerator.MoveNextAsync()) {
               Console.WriteLine(asyncEnumerator.Current);
            } 
        }
        finally { await asyncEnumerator.DisposeAsync(); }
    }
    
    ## 异步迭代器,但await不能在这些迭代器的主体中使用
    private static async IAsyncEnumerable<string> AsyncEnumerableTest()
    {
        await Task.Delay(100);
        yield return "001";
        yield return "002";
        yield return "003";
        yield return "004";
        yield return "005";
    }
    
    • Task<bool> MoveNextAsync(); T current { get; }:使用 Task<bool> 将支持使用缓存的任务对象来表示同步、成功的 MoveNextAsync 调用,但异步完成仍需要分配。 通过返回 ValueTask<bool> ,我们允许枚举器对象自身实现 IValueTaskSource<bool> 并用作从返回的的后备 ValueTask<bool> MoveNextAsync ,这反过来允许大大降低开销。
    • ValueTask<(bool, T)> MoveNextAsync();:更难使用,但这意味着它 T 不再是协变的。

    foreach 引入异步

    若要强制 foreach 改为仅考虑异步 api,请按 await 如下所示插入:

    await foreach (var i in enumerable)
    
    var enumerable = ...; 
    await foreach (T item in enumerable) {    ... } 
    
    ### 转换为的等效项:
    
    var enumerable = ...;
    var enumerator = enumerable.GetAsyncEnumerator();
    try
    {
        while (await enumerator.MoveNextAsync())
        {
           T item = enumerator.Current;
           ...
        }
    }
    finally
    {
        await enumerator.DisposeAsync(); // omitted, along with the try/finally, if the enumerator doesn't expose DisposeAsync
    }
    
    

    ConfigureAwait
    此基于模式的编译允许 ConfigureAwait 通过扩展方法在所有等待中使用

    await foreach (T item in enumerable.ConfigureAwait(false))
    {
       ...
    }
    
    ###这将基于我们还将添加到 .NET 的类型,可能会 System.Threading.Tasks.Extensions.dll:
    

    LINQ增加异步方法

    List<string> list = new List<string>();
                list.Add("001");
                list.Add("002");
                list.Add("003");
                list.Add("004");
                list.Select(AsyncMethod);
    
    async ValueTask<string> AsyncMethod(string item){
             await Task.Yield();
            return item + "-";
    }
    
    List<string> list = new List<string>();
                list.Add("001");
                list.Add("002");
                list.Add("003");
                list.Add("004");
                list.Select(async (item) =>
                {
                    await Task.Yield();
                    return item + "-";
                });
    
     public partial class Form1 : Form
        {
            CancellationTokenSource cancellationToken;
    
            public Form1()
            {
                InitializeComponent();
            }
    
            private async void button1_Click(object sender, EventArgs e)
            {
                cancellationToken = new CancellationTokenSource();
                CancellationToken token = cancellationToken.Token;
    
                IAsyncEnumerable<int> enumerable = ReadIntAsync(token);
    
                await Task.Delay(3000);
    
                //设置要在循环访问时传递到 GetAsyncEnumerator(CancellationToken) 的 CancellationToken。
                //enumerable.WithCancellation(token);
                IAsyncEnumerator<int> enumerator = enumerable.GetAsyncEnumerator();
    
                try
                {
                    while (await enumerator.MoveNextAsync())
                    {
                        Debug.WriteLine(enumerator.Current);
                    }
                }
                catch (TaskCanceledException ex1)
                {
                    Console.WriteLine(ex1.Message);
                }
                catch (OperationCanceledException ex2)
                {
                    Console.WriteLine(ex2.Message);
                }
                finally
                {
                    await enumerator.DisposeAsync();
                }
    
            }
    
    
            private void button2_Click(object sender, EventArgs e)
            {
                cancellationToken = cancellationToken??new CancellationTokenSource();
                cancellationToken.Cancel();
            }
    
            
            
            private async IAsyncEnumerable<int> ReadIntAsync([EnumeratorCancellation] CancellationToken _token)
            {
                try
                {
                    for (int i = 0; i < 20; i++)
                    {
                        //取消任务及其子级
                        if (_token.IsCancellationRequested)
                        {
                            _token.ThrowIfCancellationRequested();
                        }
                        await Task.Delay(200, _token);
                        yield return i;
                    }
                }
                finally
                {
                    Console.WriteLine("finally");
                }
            }
    
        }
    

    索引和范围

    此功能与提供两个新的运算符,它们允许构造 System.Index 和 System.Range 对象,并使用它们在运行时索引/切片集合。

    namespace System
    {
        public readonly struct Index
        {
            public Index(int value, bool fromEnd);
        }
    }
    

    若要 System.Index 在数组元素访问中使用类型作为参数,需要以下成员:

    int System.Index.GetOffset(int length);
    

    int[] arr = new int[6] { 132, 67, 47, 58, 83,100};
    Index index = new Index(8, true);
    --- 偏移量
    int offset = index.GetOffset(arr.Length);

    的 .. 语法 System.Range 需要该 System.Range 类型,以及以下一个或多个成员:

    namespace System
    {
        public readonly struct Range
        {
            public Range(System.Index start, System.Index end);
            public static Range StartAt(System.Index start);
            public static Range EndAt(System.Index end);
            public static Range All { get; }
        }
    }
    

    最后,对于 System.Range 要在数组元素访问表达式中使用的类型值,必须存在以下成员:

    //System.Runtime.CompilerServices=> 意味着编译器一开始就可以编译
    namespace System.Runtime.CompilerServices
    {
        public static class RuntimeHelpers
        {
            public static T[] GetSubArray<T>(T[] array, System.Range range);
        }
    }
    

    语言会引入新的范围运算符 x..y 。 它是一个接受两个表达式的二元中缀运算符

    System.Range operator ..(Index start = 0, Index end = ^0);
    

    .. 将运算符称为 范围运算符

     public Index(int value, bool fromEnd);
    

    运算符 ^ 等价于 Index(int value, bool fromEnd)

    示例:

    int[] arr = new int[6] { 132, 67, 47, 58, 83,100};
    //获取前三个元素 以某某作为截止
    int[] vs1 = RuntimeHelpers.GetSubArray(arr, Range.EndAt(3));
    //获取后三个元素 以某某作为开始
    int[] vs2 = RuntimeHelpers.GetSubArray(arr, Range.StartAt(3));
    Console.WriteLine(vs2);
    
     Range range1 = Range.StartAt(2);//从第三个元素开始 startIndex<=x<lenth
     Range range2 = new Range(2, 4);//从第三个元素开始 到第四个元素 startIndex<=x<endIndex
     var array = new int[] { 1, 2, 3, 4, 5 };
     var slice1 = array[range1];  
     var slice2 = array[range2];  
    

    fromEnd为true 从尾部计算下标 :offset = arr.length-index;

    new Index(index, fromEnd: true).GetOffset(array.Length)

    var array = new int[] { 1, 2, 3, 4, 5 };
    var lastItem = array[^1];    // array[new Index(1, fromEnd: true)]
    var slice1 = array[2..^3];    // array[new Range(2, new Index(3, fromEnd: true))]
    var slice2 = array[..^3];     // array[Range.EndAt(new Index(3, fromEnd: true))]
    var slice3 = array[2..];      // array[Range.StartAt(2)]
    var slice4 = array[..];       // array[Range.All]
    

    此外, System.Index 应从进行隐式转换 System.Int32 ,以避免对多维签名重载混合整数和索引的需求。

    此新优先级组低于一元运算符 ,大于乘法算术运算符

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

    内插字符串的结构

    若要将字符串标识为内插字符串,可在该字符串前面加上 符号。 字符串字面量开头的 和 " 之间不能有任何空格。 若要连接多个内插字符串,请将 $ 特殊字符添加到每个字符串字面量。
    具备内插表达式的项的结构如下所示:

     {<interpolationExpression>[,<alignment>][:<formatString>]}
    
    元素 描述
    interpolationExpression 生成需要设置格式的结果的表达式。 null 的字符串表示形式为 String.Empty
    alignment 常数表达式,如果值为正,则字符串表示形式为右对齐;如果值为负,则为左对齐。 数值为意味着字符串占位长度,如果小于表达式实际值 则以实际值长度计算。
    formatString 受表达式结果类型支持的格式字符串。 有关更多信息,请参阅格式字符串组件

    如时间格式:MM/dd/yy H:mm:ss zzz
    GUID格式: N 或者 D (见下方示例) |

    Console.WriteLine($"|{"Left000"}|{"000Right"}|");
    Console.WriteLine($"|{"Left",-7}|{"Right",7}|");
    
    ---输出结果:--
    |Left000|000Right|
    |Left   |  Right|
    -----
    
    Guid guid = Guid.NewGuid();
    Console.WriteLine($"{guid:N}");
    

    小破站: https://www.bilibili.com/video/BV1E24y147Vc
    抖茵: https://www.douyin.com/user/self?modal_id=7217801169001844007&showTab=post

    相关文章

      网友评论

          本文标题:盘点C# 8.0中好用的特性

          本文链接:https://www.haomeiwen.com/subject/wmkvddtx.html