zoukankan      html  css  js  c++  java
  • 【C#】C#8.0常用新特性

    经常在第三方.NET库中,看到一些“稀奇古怪”的写法,这是啥?没错,这可能就是有所耳闻,但是不曾尝试的C#新语法,本篇就对C#8.0中常用的一些新特性做一个总览,并不齐全,算是抛砖引玉。

    1.索引与范围

    1.1 索引

    使用^操作符:^1指向最后一个元素,^2倒数第二个元素:

    char[] vowels = new char[] {'a','e','i','o','u'};
    char lastElement  = vowels [^1];   // 'u'
    char secondToLast = vowels [^2];   // 'o'
    

    1.2 范围

    使用..操作符slice一个数组

    左闭右开

    ps:是两个点,不是es6的扩展运算符的三个点

    char[] vowels = new char[] {'a','e','i','o','u'};
    char[] firstTwo =  vowels [..2];    // 'a', 'e'
    char[] lastThree = vowels [2..];    // 'i', 'o', 'u'
    char[] middleOne = vowels [2..3]    // 'i'
    char[] lastTwo =   vowels [^2..];   // 'o', 'u'
    

    1.3 Index类型与Range类型

    主要借助于Index类型和Range类型实现索引与范围

    char[] vowels = new char[] {'a','e','i','o','u'};
    Index last = ^1; 
    Range firstTwoRange = 0..2; 
    char[] firstTwo = vowels [firstTwoRange];   // 'a', 'e'
    

    1.4 扩展-索引器

    可以定义参数类型为Index或Range的索引器

    class Sentence
    {
      string[] words = "The quick brown fox".Split();
      public string this   [Index index] => words [index];
      public string[] this [Range range] => words [range];
    }
    

    2.空合并操作

    ??=

    string s=null;
    if(s==null)
        s="Hello,world";//s==null,s is Hello,world,or s is still s
    
    //you can write this
    s??="hello,world";
    
    

    3.using声明

    如果省略了using后面的{},及声明语句块,就变为了using declaration,当执行落到所包含的语句块之外时,该资源将被释放

    if (File.Exists ("file.txt"))
    {
      using var reader = File.OpenText ("file.txt");
      Console.WriteLine (reader.ReadLine());
      ...
    }
    

    当执行走到if语句块之外时,reader才会被释放

    4.readonly成员

    允许在结构体的函数中使用readonly修饰符,确保如果函数试图修改任何字段,会产生编译时错误:

    struct Point
    {
      public int X, Y;
      public readonly void ResetX() => X = 0;  // Error!
    }
    

    如果一个readonly函数调用一个非readonly成员,编译器会产生警告。

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

    readonly ToString()调用Distance

    warning CS8656: Call to non-readonly member 'Point.Distance.get' from a 'readonly' member results in an implicit copy of 'this'

    Distance 属性不会更改状态,因此可以通过将 readonly 修饰符添加到声明来修复此警告

    5.静态本地函数

    在本地函数加上static,以确保本地函数不会从封闭范围捕获任何变量。 这样做会生成 CS8421,“静态本地函数不能包含对

    int M()
    {
        int y = 5;
        int x = 7;
        return Add(x, y);
    
        static int Add(int left, int right) => left + right;
    }
    

    Add()不访问封闭范围中的任何变量,当然如果Add函数访问了本地变量,那就有问题:

    int M()
    {
        int y;
        LocalFunction();
        return y;
    
        static void LocalFunction() => y = 0; //warning
    }
    

    6.默认接口成员

    允许向接口成员添加默认实现,使其成为可选实现:

    interface ILogger
    {
      void Log (string text) => Console.WriteLine (text);
    }
    

    默认实现必须显式接口调用:

    class Logger : ILogger { }
    ...
    ((ILogger)new Logger()).Log ("message");
    

    接口也可以定义静态成员(包括字段),然后可以在默认实现中访问:

    interface ILogger
    {
      void Log (string text) => Console.WriteLine (Prefix + text);
      static string Prefix = "";
    }
    

    或者在接口外部,因为接口成员是隐式公共的,所以还可以从接口外部访问静态成员的:

    ILogger.Prefix = "File log: ";
    

    除非给静态接口成员加上(private,protected,or internal)加以限制,实例字段是禁止的。

    7.switch表达式

    可以在一个表示式上下文中使用switch

    string cardName = cardNumber switch
    {
      13 => "King",
      12 => "Queen",
      11 => "Jack",
      _ => "Pip card"   // equivalent to 'default'
    };
    

    注意:switch关键字是在变量名之后,{}里面是case子句,要逗号。switch表达式比switch块更简洁,可以使用LINQ查询。

    如果忘记了默认表达式_(这个像不像golang的匿名变量),然后switch匹配失败。会抛异常的。

    还可以基于元组匹配

    int cardNumber = 12;
    string suit = "spades";
    
    string cardName = (cardNumber, suit) switch
    {
      (13, "spades") => "King of spades",
      (13, "clubs") => "King of clubs",
      ...
    };
    

    还可以使用属性模式

    System.Uri 类,具有属性Scheme, Host, Port, 和 IsLoopback.考虑如下场景:写一个防火墙,我们可以使用switch表达式的属性模式来决定防火墙的规则(阻止或允许):

    bool ShouldAllow (Uri uri) => uri switch
    {
      { Scheme: "http",  Port: 80  } => true,
      { Scheme: "https", Port: 443 } => true,
      { Scheme: "ftp",   Port: 21  } => true,
      { IsLoopback: true           } => true,
      _ => false
    };
    

    还可以使用位置模式

    包含可以访问的解构函数的Point类,可以使用位置模式

    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
    }
    

    使用位置模式 来提取 xy 的值。 然后,它使用 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
    };
    

    8.可空引用类型

    使用可空类型,可有效避免NullReferenceExceptions,但是如果编译器认为异常还是可能会出现,就会发出警告。

    void Foo (string? s) => Console.Write (s.Length);  // Warning (.Length)
    

    如果需要移除警告,可以使用null-forgiving operator(!)

    void Foo (string? s) => Console.Write (s!.Length);
    

    当然上面的例子是很危险的,因为实际上,这个字符串是可能为NULL的。

    void Foo (string? s)
    {
      if (s != null) Console.Write (s.Length);
    }
    

    上面的例子,就不需要!操作符,因为编译器通过静态流分析(static flow analysis`)且足够智能,分析出代码是不可能抛出ReferenceException的。当然编译器分析也不是万能的,比如在数组中,它就不能知道数组元素哪些有数据,哪些没有被填充,所以下面的内容就不会生成警告:

    var strings = new string[10];
    Console.WriteLine (strings[0].Length);
    

    9.异步流

    在C#8.0之前,可以使用yield返回一个迭代器(iterator),或者使用await编写异步函数。但是如果想在一个异步函数返回一个迭代器怎么办?

    C#8.0引入了异步流-asynchronous streams,解决了这个问题

    //async IAsyncEnumerable
    async IAsyncEnumerable<int> RangeAsync (int start, int count, int delay)
    {
      for (int i = start; i < start + count; i++)
      {
        await Task.Delay (delay);
        yield return i;
      }
    }
    

    await foreach调用异步流

    await foreach (var number in RangeAsync (0, 10, 100))
      Console.WriteLine (number);
    

    LINQ查询

    这个需要System.Linq.Async

    IAsyncEnumerable<int> query =
    from i in RangeAsync (0, 10, 500)
      where i % 2 == 0   // Even numbers only.
      select i * 10;     // Multiply by 10.
    
    
    await foreach (var number in query)
      Console.WriteLine (number);
    

    ASP.Net Core

    [HttpGet]
    public async IAsyncEnumerable<string> Get()
    {
        using var dbContext = new BookContext();
        await foreach (var title in dbContext.Books
                                             .Select(b =>b.Title)
                                             .AsAsyncEnumerable())
        yield return title;
    }
    

    可释放

    实现 System.IAsyncDisposable 即可,可以使用using自动调用,亦可手动实现IAsyncDisposable接口

    10.字符串插值

    $@ 标记的顺序可以任意安排:$@"..."@$"..." 均为有效的内插逐字字符串,这个在C#6.0时,是有严格的顺序限制。

  • 相关阅读:
    磁盘原理总结
    Algorithm:多维数组和矩阵
    Algorithm:字典序最小问题
    Algorithm:递归思想及实例分析
    Algorithm:贪心策略之区间覆盖问题
    Algorithm:贪心策略之区间选点问题
    Algorithm:位运算的这些小技巧你知道吗?
    面试题
    操作系统
    数据结构:B树和B+树的插入、删除图文详解
  • 原文地址:https://www.cnblogs.com/RandyField/p/13138814.html
Copyright © 2011-2022 走看看