CSharp的函数式特性和注意
CSharp的函数式特性和注意
‘祈止’函数式编程
随着编程语言技术的不断演进,函数式编程因其更加安全的特性,逐渐成为业内的主流方向。
C#中也添加了许多函数式的编程特性,这些特性可以带来不少遍历,但也有可能造成性能压力。
LINQ
LINQ (Language Integrated Query, 语言集合查询) 是C#一套API,可以使用类似SQL语言声明式语法来查询和操作内存中的数据集(如:数组,列表)。
好处是:简介、可读
1 | var adults = people.Where(p => p.Age >= 18).ToList(); |
**工作原理:**通过 IEnumerable<T> 接口上定义的一些列扩展方法。
有:Where,Select,OrderBy。
在unity中不宜使用
在Unity中使用时,会导致较大GC压力。尤其是使用在Mono中的update里使用。频繁的堆分配回迅速堆积垃圾,触发GC导致卡顿。
原理:
隐式内存分配:
- 迭代器:使用
Where这种惰性求职方法,不会立刻执行。会创建一个IEnumerable<T>接口的迭代器对象,用于记录当前查询状态。 - 中间集合:
OrderBy,ToList()等即使求值方法会立即执行,会在栈上创建一个新的集合存放数据。
- 迭代器:使用
Lambda表达式和闭包:
- 使用LINQ,经常编写Lambda表达式(如:
e => e.IsDead)。可能被编译场委托,创建产生内存分配。 - 当Lambda表达式捕获了外部变量,会生成一个闭包类来存储变量。每次使用都会产生内存分配。
- 使用LINQ,经常编写Lambda表达式(如:
值类型的装箱(Boxing):
- LINQ操作如果涉及将值类型(如:
int、float、struct)隐式转换为引用类型object,就会发生装箱。会在堆上产生内存分配。
- LINQ操作如果涉及将值类型(如:
执行效率低:除了内存分配问题,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+)
使用
and、or、not组合多个条件,取代&&和||。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, ..])。



