第十九节:语法总结(4)之C# 7.x、8.0、9.0新语法

一. C#7.x总结

参考:https://docs.microsoft.com/zh-cn/dotnet/csharp/whats-new/csharp-7

1.弃元

(1).含义

  从 C# 7.0 开始,C# 支持弃元,这是一种在应用程序代码中人为取消使用的【占位符变量】、。 弃元相当于未赋值的变量;它们没有值。 因为只有一个弃元变量,甚至不为该变量分配存储空间,所以弃元可减少内存分配。 因为它们使代码的意图清楚,增强了其可读性和可维护性

(2). 使用场景

 A. 元组和对象析构。 

public class Example
{
    public static void Main()
    {
        var (_, _, _, pop1, _, pop2) = QueryCityDataForYears("New York City", 1960, 2010);

        Console.WriteLine($"Population change, 1960 to 2010: {pop2 - pop1:N0}");
    }

    private static (string, double, int, int, int, int) QueryCityDataForYears(string name, int year1, int year2)
    {
        int population1 = 0, population2 = 0;
        double area = 0;

        if (name == "New York City")
        {
            area = 468.48;
            if (year1 == 1960)
            {
                population1 = 7781984;
            }
            if (year2 == 2010)
            {
                population2 = 8175133;
            }
            return (name, area, year1, population1, year2, population2);
        }

        return ("", 0, 0, 0, 0, 0);
    }
}
View Code

 B. 使用 is 和 switch 的模式匹配。

public class Example
{
   public static void Main()
   {
      object[] objects = { CultureInfo.CurrentCulture,
                           CultureInfo.CurrentCulture.DateTimeFormat,
                           CultureInfo.CurrentCulture.NumberFormat,
                           new ArgumentException(), null };
      foreach (var obj in objects)
         ProvidesFormatInfo(obj);
   }

   private static void ProvidesFormatInfo(object obj)
   {
      switch (obj)
      {
         case IFormatProvider fmt:
            Console.WriteLine($"{fmt} object");
            break;
         case null:
            Console.Write("A null object reference: ");
            Console.WriteLine("Its use could result in a NullReferenceException");
            break;
         case object _:
            Console.WriteLine("Some object type without format information");
            break;
      }
   }
}
View Code

 C. 使用out参数的弃元 DateTime.TryParse(String, out DateTime)

public class Example
{
   public static void Main()
   {
      string[] dateStrings = {"05/01/2018 14:57:32.8", "2018-05-01 14:57:32.8",
                              "2018-05-01T14:57:32.8375298-04:00", "5/01/2018",
                              "5/01/2018 14:57:32.80 -07:00",
                              "1 May 2018 2:57:32.8 PM", "16-05-2018 1:00:32 PM",
                              "Fri, 15 May 2018 20:10:57 GMT" };
      foreach (string dateString in dateStrings)
      {
         if (DateTime.TryParse(dateString, out _))
            Console.WriteLine($"'{dateString}': valid");
         else
            Console.WriteLine($"'{dateString}': invalid");
      }
   }
}
View Code

 D. 独立弃元。 可使用独立弃元来指示要忽略的任何变量。 以下示例使用独立占位符来忽略异步操作返回的 Task 对象。

private static async Task ExecuteAsyncMethods()
   {
      Console.WriteLine("About to launch a task...");
      _ = Task.Run(() => { var iterations = 0;
                           for (int ctr = 0; ctr < int.MaxValue; ctr++)
                              iterations++;
                           Console.WriteLine("Completed looping operation...");
                           throw new InvalidOperationException();
                         });
      await Task.Delay(5000);
      Console.WriteLine("Exiting after 5 second delay");
   }
View Code

 E. C# 9.0 开始,可以使用弃元指定 Lambda 表达式中不使用的输入参数

2. 元祖

  详见:https://www.cnblogs.com/yaopengfei/p/9061819.html

3. 模式匹配

 模式匹配支持 is 表达式和 switch 表达式。 每个表达式都允许检查对象及其属性以确定该对象是否满足所寻求的模式。 使用 when 关键字来指定模式的其他规则。

  switch 匹配表达式具有常见的语法,它基于已包含在 C# 语言中的 switch 语句。 更新后的 switch 语句有几个新构造:

  switch 表达式的控制类型不再局限于整数类型、Enum 类型、string 或与这些类型之一对应的可为 null 的类型。 可能会使用任何类型。

  可以在每个 case 标签中测试 switch 表达式的类型。 与 is 表达式一样,可以为该类型指定一个新变量。

  可以添加 when 子句以进一步测试该变量的条件。

  case 标签的顺序现在很重要。 执行匹配的第一个分支;其他将跳过。

代码分享:

public static int SumPositiveNumbers(IEnumerable<object> sequence)
{
    int sum = 0;
    foreach (var i in sequence)
    {
        switch (i)
        {
            case 0:
                break;
            case IEnumerable<int> childSequence:
            {
                foreach(var item in childSequence)
                    sum += (item > 0) ? item : 0;
                break;
            }
            case int n when n > 0:
                sum += n;
                break;
            case null:
                throw new NullReferenceException("Null found in sequence");
            default:
                throw new InvalidOperationException("Unrecognized type");
        }
    }
    return sum;
}

剖析:

  • case 0: 是常见的常量模式。
  • case IEnumerable<int> childSequence: 是一种类型模式。
  • case int n when n > 0: 是具有附加 when 条件的类型模式。
  • case null: 是 null 模式。
  • default: 是常见的默认事例。

4. 异步 main 方法

 支持:static async Task<int> Main(){};

 支持:static async Task Main(){};

5. 本地(Local)函数

 方法中再声明一个方法

public Task<string> PerformLongRunningWork(string address, int index, string name)
{
    if (string.IsNullOrWhiteSpace(address))
        throw new ArgumentException(message: "An address is required", paramName: nameof(address));
    if (index < 0)
        throw new ArgumentOutOfRangeException(paramName: nameof(index), message: "The index must be non-negative");
    if (string.IsNullOrWhiteSpace(name))
        throw new ArgumentException(message: "You must supply a name", paramName: nameof(name));

    return longRunningWorkImplementation();

    async Task<string> longRunningWorkImplementation()
    {
        var interimResult = await FirstWork(address);
        var secondResult = await SecondStep(index, name);
        return $"The results are {interimResult} and {secondResult}. Enjoy.";
    }
}

7. 默认委托表达式

旧写法

Func<string, bool> whereClause = default(Func<string, bool>);

新写法

Func<string, bool> whereClause = default;

8. 数字语法改进

  增强可读性 public const long BillionsAndBillions = 100_000_000_000;

9. out变量

 可以在方法调用的参数列表中声明 out 变量,而不是编写单独的声明语句,不需要先单独声明了

if (int.TryParse(input, out int result))
    Console.WriteLine(result);
else
    Console.WriteLine("Could not parse input");

10. in参数修饰符

 in 关键字补充了现有的 ref 和 out 关键字,以按引用传递参数。 in 关键字指定按引用传递参数,但调用的方法不修改值。

 static void M(S arg);

 static void M(in S arg);

11. stackalloc 数组支持初始值设定项

详见下面代码

int* pArr = stackalloc int[3] {1, 2, 3};
int* pArr2 = stackalloc int[] {1, 2, 3};
Span<int> arr = stackalloc [] {1, 2, 3};

 

二. C#8.0总结

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

1.模式匹配

(1).Switch表达式

 A.变量位于 switch 关键字之前。 不同的顺序使得在视觉上可以很轻松地区分 switch 表达式和 switch 语句。

 B.将 case 和 : 元素替换为 =>。 它更简洁,更直观。

 C.将 default 事例替换为 _ 弃元。

 D.正文是表达式,不是语句。

public enum Rainbow
{
    Red,
    Orange,
    Yellow,
    Green,
    Blue,
    Indigo,
    Violet
}
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)),
    };

(2).属性模式

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

      /// <summary>
        /// 属性模式
        /// state是Adress的属性
        /// </summary>
        /// <param name="location"></param>
        /// <param name="salePrice"></param>
        /// <returns></returns>
        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
        };
 public class Address
    {
        public string state { get; set; }

        public string xxx1 { get; set; }

        public string xxx2 { get; set; }
    }
View Code

(3).元组模式

 一些算法依赖于多个输入。 使用元组模式,可根据表示为元组的多个值进行切换。 以下代码显示了游戏“rock, paper, scissors(石头剪刀布)”的切换表达式::

       /// <summary>
        /// 元祖模式
        /// </summary>
        /// <param name="first"></param>
        /// <param name="second"></param>
        /// <returns></returns>
        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"
        };

(4).位置模式

代码分享

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

 2. 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++;
        }
    }
    // Notice how skippedLines is in scope here.
    return skippedLines;
    // file is disposed here
}
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;
    } // file is disposed here
}
View Code

3.静态本地函数

 可以在方法内部声明一个内部的静态方法。

int M()
{
    int y = 5;
    int x = 7;
    return Add(x, y);

    static int Add(int left, int right) => left + right;
}

4.可以设置空引用类型

  通过? 符号来设置

5.索引和范围

 (1).末尾运算符 ^ 的索引,指定一个索引与序列末尾相关.

     ^1表示倒数第一个元素。 ^2表示倒数第二个元素

 (2).范围运算符 ..,用于指定范围的开始和末尾,就像操作数一样。 也可以声明变量: Range r1 = 1..4;

            {
                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($"第一个元素为:{words[0]}");
                //倒数第一、二个元素
                Console.WriteLine($"倒数第一个元素为:{words[^1]}");
                Console.WriteLine($"倒数第二个元素为:{words[^2]}");

                //范围
                var p1 = words[..];   // 全部元素
                var p2 = words[..4];  // 前4个元素
                var p3 = words[6..];  // 从第7个元素到最后
                var p4 = words[1..4]; //获取第2,3,4个元素

                //也可以先声明变量
                Range r1 = 1..4;
                var p5 = words[r1];

            }

6.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);

                //最终输出结果是 两个 17
                foreach (var item in numbers)
                {
                    Console.WriteLine(item);
                }

            }

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

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

  {
                string str = "ypf";
                string a1 = $@"lskdfjslf{str}slkfslf";
                string a2 = @$"lskdfjslf{str}slkfslf";
  }

 

三. C#9.0总结

(.Net 5.x 支持C# 9.0)

1.记录类型

 C# 9.0 引入了记录类型,这是一种引用类型,它提供合成方法来提供值语义,从而实现相等性。 默认情况下,记录是不可变的。使用记录类型可在.NET 中轻松创建不可变的引用类型

public record Person
{
    public string LastName { get; }
    public string FirstName { get; }

    public Person(string first, string last) => (FirstName, LastName) = (first, last);
}

2.顶级语句

 System.Console.WriteLine("Hello World!");可以删除 using System;

3.模式匹配增强功能

 类型模式要求在变量是一种类型时匹配

 带圆括号的模式强制或强调模式组合的优先级

 联合 and 模式要求两个模式都匹配

 析取 or 模式要求任一模式匹配

 求反 not 模式要求模式不匹配

 关系模式要求输入小于、大于、小于等于或大于等于给定常数。

        //模式匹配增强
 public  bool IsLetter(char c) =>
     c is >= 'a' and <= 'z' or >= 'A' and <= 'Z';
 public  bool IsLetterOrSeparator(char c) =>
     c is (>= 'a' and <= 'z') or (>= 'A' and <= 'Z') or '.' or ',';

4.调整和完成功能

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

 如:List<string> list1 = new();

private List<WeatherObservation> _observations = new();
WeatherStation station = new() { Location = "Seattle, WA" };
List<string> list1 = new();

 

 

 

 

 

 

 

!

  • 作       者 : Yaopengfei(姚鹏飞)
  • 博客地址 : http://www.cnblogs.com/yaopengfei/
  • 声     明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
  • 声     明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
 
posted @ 2021-02-03 14:34  Yaopengfei  阅读(1195)  评论(1编辑  收藏  举报