CSharp的函数式特性和注意

函数式编程

随着编程语言技术的不断演进,函数式编程因其更加安全的特性,逐渐成为业内的主流方向。

C#中也添加了许多函数式的编程特性,这些特性可以带来不少遍历,但也有可能造成性能压力。

LINQ

LINQ (Language Integrated Query, 语言集合查询) 是C#一套API,可以使用类似SQL语言声明式语法来查询和操作内存中的数据集(如:数组,列表)。


好处是:简介、可读

1
2
var adults = people.Where(p => p.Age >= 18).ToList();
// 查询所有成年人,返回一个列表。

**工作原理:**通过 IEnumerable<T> 接口上定义的一些列扩展方法。

有:WhereSelectOrderBy

在unity中不宜使用

在Unity中使用时,会导致较大GC压力。尤其是使用在Mono中的update里使用。频繁的堆分配回迅速堆积垃圾,触发GC导致卡顿。

原理:

  • 隐式内存分配

    • 迭代器:使用Where这种惰性求职方法,不会立刻执行。会创建一个IEnumerable<T>接口的迭代器对象,用于记录当前查询状态。
    • 中间集合:OrderBy,ToList()即使求值方法会立即执行,会在栈上创建一个新的集合存放数据。
  • Lambda表达式和闭包:

    • 使用LINQ,经常编写Lambda表达式(如:e => e.IsDead)。可能被编译场委托,创建产生内存分配。
    • 当Lambda表达式捕获了外部变量,会生成一个闭包类来存储变量。每次使用都会产生内存分配。
  • 值类型的装箱(Boxing):

    • LINQ操作如果涉及将值类型(如:intfloatstruct)隐式转换为引用类型object,就会发生装箱。会在堆上产生内存分配。
  • 执行效率低:除了内存分配问题,LINQ本身的执行效率也低于手写循环。有基准测试显示、相同逻辑情况下,LINQ是手写时长的19倍

操作重灾区

  • OrderBy / OrderByDescending:排序需要收集所有元素并比较,必定产生大量内存分配
  • ToList() / ToArray():强制立即执行并创建新集合,一定会分配新内存。
  • GroupBy / Join:这些复杂操作内部需要构建临时查找表或集合
  • OfType<T>() / Cast<T>():涉及类型转换,可能产生额外的迭代器开销。

模式匹配

一种声明式的、具有“解构”能力的数据检查语法

is表达式

  • 类型模式(最常用):

    • 类型检查创建变量合二为一

    • object obj = "hello";
      
      if(obj is string s){// 判读obj是不是string,是就赋值给s
          Console.WriteLine(s.Length);// 直接使用s
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12

      - **常量模式**

      - 将**变量**与**常量**(字面量或`const`)比较。

      - ~~~c#
      int value = 100;
      // 相当于 value == 100
      if(value is 100){...}

      // 结合null检查很实用
      if(obj is null){...}
  • 属性模式(好用)

    • 检查对象的属性值是否符合条件,同时可以提取子属性。

    • class Person { 
          public string Name { get; set;}  
          public int Age { get; set;} 
      }
      
      Person p = new Person { Name = "张三", Age = 18 };
      
      // 匹配属性
      if(p is { Name:"张三", Age: >= 18 }) {...}
      // 提取嵌套属性
      // 或者{ Name: var name }
      if(p is { Name: { Length: var len} }) {
          Console.WriteLine($"名字长度:{len}");
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11

      - **位置模式(解构匹配)**

      - 结合**解构(Deconstruction)**,将元组或可解构对象的元素拆开匹配,用于**多值匹配**。

      - ~~~
      var point = (X: 0, Y: 0);
      if(point is (0, 0)) {...}

      // 匹配x=0,提取y
      if(customPoint is (0, var y)) {...}
  • 逻辑模式(C# 9.0+)

    • 使用andornot组合多个条件,取代&&||

    • int score = 85;
      if(score is >= 80 and < 90){...}
      // 对象
      if(obj is not null){...}
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14

      - **弃元模式**

      - 用``_``忽略不关心的值

      - ~~~c#
      // 只关心第二个元素
      if (point is (_, 0)) // 匹配 y=0,忽略 x
      {
      Console.WriteLine("在 X 轴上");
      }

      // switch 默认分支
      _ => throw new NotImplementedException()
  • 列表模式

    • 匹配数组或集合的结构和元素,支持切片..

    • int[] arr = { 1, 2, 3 };
      
      // 匹配:第一个元素是1,最后一个元素是3
      if (arr is [1, .., 3])
      {
          Console.WriteLine("首尾匹配");
      }
      
      // 匹配:以 1,2 开头,剩余元素存入 rest
      if (arr is [1, 2, .. var rest])
      {
          Console.WriteLine($"剩余长度: {rest.Length}");
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32

      ### ``switch``表达式(终极形态)

      ``switch``表达式是模式匹配最强大的应用,**没有case和break**,用``=>``直接映射条件和结果。

      ---

      ~~~c#
      // 计算形状面积
      double GetArea(object shape) => shape switch
      {
      // 类型 + 属性模式:提取 Radius
      Circle { Radius: var r } => Math.PI * r * r,

      // 位置模式 + 逻辑模式:解构并加条件
      Rectangle (var w, var h) when w > 0 && h > 0 => w * h,

      // 常量模式
      null => 0,

      // 默认分支(必须存在,除非穷举)
      _ => throw new ArgumentException("未知形状")
      };

      // 配合枚举使用(C# 8.0 支持 switch 表达式匹配枚举)
      string GetStatus(Status s) => s switch
      {
      Status.Success => "成功",
      Status.Pending => "等待中",
      Status.Failed => "失败",
      _ => "未知" // 即使枚举已穷举,也建议保留 _
      };

关键

  • 穷举性检查:编译器会检查是否覆盖了所有可能的情况(如枚举类型),未覆盖会给出警告。

  • when 子句:可以在模式后附加额外的条件(when age > 18)。

实战建议(何时选用哪种模式)

  • 简单类型判断:用 if (obj is T t)首选
  • 单属性条件:用 if (obj is { Prop: > 10 })
  • 元组/多值判断:用位置模式 if (tuple is (0, var y))
  • 多分支路由(3种以上):用 switch 表达式。
  • 嵌套条件(枚举 + 附加逻辑):用 switch 表达式 + when 子句。
  • 集合结构检查(C# 11+):用列表模式 if (arr is [1, ..])