从C#7.0开始,推出了一种新的特性:弃元,这种思想可能来源于Golang。

弃元,就是不想要了的元素变量,用单下划线(_)表示,弃元在编译时起作用,就是搞编译器:这个变量我不要,你可以优化处理。

我们经常在下面几个过程中使用弃元:

1、元组解构赋值

  • 在使用元组解构赋值时,我们往往需要接收元组的所有项,而当我们是需要元组中的某些指定项时,就可以使用弃元了:  
  1. static (int, char, bool, string, object) GetTuple()
  2. {
  3. return (1, 'a', true, "hello", new object());
  4. }
  5. static void Main(string[] args)
  6. {
  7. var (i, c, b, s, obj) = GetTuple();//接收所有项
  8. //接收部分项
  9. var (i, _, _, _, _) = GetTuple();
  10. (_, _, _, _, object obj) = GetTuple();
  11. (_, char c, _, string s, _) = GetTuple();
  12. }

2、在switch表达式中使用弃元

  在switch表达式中,经常看到弃元的存在,因为switch表达式是输出表达式,当表达式中的每一项均不匹配时,它会抛出异常,而弃元可以消除这种异常,switch表达式中的弃元的作用就类似于switch语句中的default语句的作用:

  1. public class Point2D
  2. {
  3. public int X { get; set; }
  4. public int Y { get; set; }
  5. public void Deconstruct(out int x, out int y) => (x, y) = (X, Y);
  6. }
  7. static void Print(object point)
  8. {
  9. (var x, var y) = point switch
  10. {
  11. Point2D(> 0, > 0) p => p,
  12. _ => new Point2D()//如果没有弃元,当上面模式不匹配时将抛出异常
  13. };
  14. }

3、out关键字的参数

  在一些方法中可以看到有out关键字修饰的参数,但有时我们并不关心out参数输出的结果,这时就可以使用弃元来简化:  

  1. string value = "2021-07-14 18:00:00";
  2. bool isInt = int.TryParse(value, out _);//判断结果是否可以转换成int类型,而不关系转换成int类型后的值
  3. bool isBool = bool.TryParse(value, out _);//判断结果是否可以转换成bool类型,而不关系转换成bool类型后的值
  4. //判断结果是否可以转换成DateTime类型,而不关系转换成DateTime类型后的值
  5. bool isDateTime = DateTime.TryParseExact(value, "yyyy-MM-dd HH:mm:ss", System.Globalization.CultureInfo.CurrentCulture, System.Globalization.DateTimeStyles.None, out _);

4、接收无用的表达式输出

  • 弃元可以将一些表达式的结果标记为舍弃,从而让表达式合法化,否则编译器可能会抛出警告:  
  1. //直接写1+1不合法,编译器无法通过,但是下面的写法是合法的
  2. _ = 1 + 1;
  3.   还可以与 null合并运算符?? 结合,进行空指针验证:  
  4. public void Method(object arg)
  5. {
  6. //原来的写法
  7. if (arg == null)
  8. {
  9. throw new ArgumentNullException();
  10. }
  11. //等价写法
  12. _ = arg ?? throw new ArgumentNullException();//代替if验证空指针引用
  13. }
  • 我们通常使用async-await进行异步编程,当我们在一个async方法中启用一个异步操作而为使用await时,编译器会发出警告:
  • 而弃元的存在可以让我们消除这种警告:  
  1. static async Task Print()
  2. {
  3. _ = Task.Run(() =>
  4. {
  5. //something
  6. });
  7. await Task.CompletedTask;
  8. }

注意点

  • 弃元表示不在使用的元素变量,因此一旦你使用弃元去接收值,那么就不应该通过弃元去获取值。但是,弃元符号(_)是一个合法变量符,我们可以声明单下划线的变量,一旦声明了弃元符号的变量,那么在后续的同一作用域要注意使用的是弃元还是变量!

  • 举个例子,可以好好体会:  

  1. static async Task Print()
  2. {
  3. //这里是弃元
  4. var (_, i) = (1, 2);
  5. (var _, var j) = (1, 2);
  6. string _ = "discards";//声明弃元符号字符串变量
  7. //这里是弃元,自动识别是弃元
  8. var (_, m) = (1, 2);
  9. (var _, var n) = (1, 2);
  10. var s = i switch
  11. {
  12. _ => _ //前面是弃元,后面是变量
  13. };
  14. Console.WriteLine(_);//识别为变量,输出discards
  15. //报错,此时识别为字符串
  16. bool isInt = int.TryParse(s, out _);
  17. //报错,此时识别为字符串
  18. _ = Task.Run(() =>
  19. {
  20. //something
  21. });
  22. await Task.CompletedTask;
  23. }