C# 高级教程 - 18 函数式编程

C# 是一门多范式语言,在面向对象基础上不断吸收函数式编程(FP)的特性。从 LINQ 到模式匹配,从 record 到不可变数据,C# 的函数式表达能力持续增强。本文将深入探讨不可变性、纯函数、高阶函数、Either/Option 模式、以及如何在 C# 中实践函数式设计。

不可变数据 —— record

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// record 提供基于值的相等性和 with 表达式
public record Person(string Name, int Age);

var person1 = new Person("张三", 28);
var person2 = person1 with { Age = 29 }; // 创建新实例(不可变)

Console.WriteLine(person1 == person2); // false
Console.WriteLine(person1 with { } == person1); // true

// 位置参数 record —— 解构
var (name, age) = person1;
Console.WriteLine($"{name} 今年 {age} 岁");

// 值相等
var p1 = new Person("李四", 30);
var p2 = new Person("李四", 30);
Console.WriteLine(p1 == p2); // true(结构相等)
Console.WriteLine(ReferenceEquals(p1, p2)); // false(引用不同)

纯函数与副作用隔离

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
33
34
35
36
37
// 不纯的函数 —— 依赖/修改外部状态
private int _total;
public int ImpureAdd(int a, int b)
{
_total += a + b; // 副作用:修改外部状态
Console.WriteLine($"加了 {a + b}"); // 副作用:IO
return _total;
}

// 纯函数 —— 相同的输入总是相同的输出,无副作用
public static int PureAdd(int a, int b)
{
return a + b;
}

// 实践:将副作用推到边界(函数式核心 + 命令式外壳)
public class OrderProcessor
{
// 纯函数核心 —— 可测试、可组合
public static OrderState CalculateDiscount(OrderState state)
{
if (state.Total >= 1000)
return state with { Discount = state.Total * 0.1m };
return state;
}

// 含副作用的边界
public async Task<OrderResult> ProcessOrder(Order order)
{
var state = new OrderState(order.Total);
state = CalculateDiscount(state); // 纯
// 副作用在函数之外
await _db.SaveOrderAsync(order);
await _email.SendAsync(order);
return new OrderResult(order.Id, state.Discount);
}
}

高阶函数

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
// 函数作为参数
public static T Pipe<T>(T value, params Func<T, T>[] transforms)
{
var result = value;
foreach (var fn in transforms)
result = fn(result);
return result;
}

// 函数作为返回值
public static Func<int, int> Compose(params Func<int, int>[] funcs)
{
return input =>
{
var result = input;
foreach (var fn in funcs.Reverse())
result = fn(result);
return result;
};
}

// 使用
var addOne = (int x) => x + 1;
var doubleIt = (int x) => x * 2;
var square = (int x) => x * x;

var pipeline = Pipe(5, addOne, doubleIt, square); // (5+1)*2 = 12, 12² = 144
var composed = Compose(square, doubleIt, addOne); // (5+1) → 6*2 → 12² = 144

Either/Option 模式

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
// Either<L, R> —— 表示成功或失败(代替异常)
public class Either<L, R>
{
private readonly L? _left;
private readonly R? _right;
private readonly bool _isRight;

private Either(L left)
{
_left = left;
_isRight = false;
}

private Either(R right)
{
_right = right;
_isRight = true;
}

public static Either<L, R> Success(R right) => new(right);
public static Either<L, R> Failure(L left) => new(left);

public T Match<T>(Func<L, T> leftFunc, Func<R, T> rightFunc)
=> _isRight ? rightFunc(_right!) : leftFunc(_left!);

public Either<L, T> Map<T>(Func<R, T> map)
=> _isRight
? Either<L, T>.Success(map(_right!))
: Either<L, T>.Failure(_left!);
}

// 使用
Either<string, int> ParseInt(string input)
{
return int.TryParse(input, out int value)
? Either<string, int>.Success(value)
: Either<string, int>.Failure($"无法解析: {input}");
}

Either<string, int> result = ParseInt("42");
string message = result.Match(
left: error => $"错误: {error}",
right: value => $"成功: {value}");

// 链式调用
var final = ParseInt("42")
.Map(x => x * 2)
.Map(x => x.ToString());

// Option<T>(Maybe 模式)
public class Option<T>
{
private readonly T? _value;

private Option(T value) { _value = value; }
private Option() { }

public static Option<T> Some(T value) => new(value);
public static Option<T> None() => new();

public Option<U> Bind<U>(Func<T, Option<U>> f)
=> _value is null ? Option<U>.None() : f(_value);

public Option<U> Map<U>(Func<T, U> f)
=> _value is null ? Option<U>.None() : Option<U>.Some(f(_value));

public T ValueOr(T defaultValue)
=> _value ?? defaultValue;
}

模式匹配进阶

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
// 位置模式
public record Point(int X, int Y);

static string DescribePosition(Point p) => p switch
{
(0, 0) => "原点",
(0, _) => "Y 轴上",
(_, 0) => "X 轴上",
var (x, y) when x == y => "对角线上",
_ => $"({p.X}, {p.Y})"
};

// 列表模式(.NET 6+)
static string DescribeList(int[] numbers) => numbers switch
{
[] => "空数组",
[var first] => $"单个元素: {first}",
[var first, var second] => $"两个元素: {first}, {second}",
[_, _, _, ..] => "至少三个元素",
_ => "其他"
};

// 属性模式
record Product(string Name, decimal Price, bool IsAvailable);

static decimal CalculateDiscount(Product product) => product switch
{
{ IsAvailable: false } => 0,
{ Price: > 1000, Name: var name } => product.Price * 0.15m,
{ Price: > 500 } => product.Price * 0.1m,
_ => product.Price * 0.05m,
};

函数式异常处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 将异常包装为值
public static Result<T> TryCatch<T>(Func<T> func)
{
try
{
return Result<T>.Success(func());
}
catch (Exception ex)
{
return Result<T>.Failure(ex);
}
}

// 应用
var result = TryCatch(() => File.ReadAllText("config.json"));
result.Match(
content => Console.WriteLine($"文件内容: {content}"),
error => Console.WriteLine($"读取失败: {error.Message}")
);

柯里化与部分应用

1
2
3
4
5
6
7
8
9
10
11
12
13
// 柯里化 —— 将多参数函数转换为单参数函数链
Func<int, Func<int, int>> CurriedAdd = a => b => a + b;
var add5 = CurriedAdd(5); // 部分应用
Console.WriteLine(add5(3)); // 8

// 泛型柯里化辅助方法
public static Func<T2, TResult> Partial<T1, T2, TResult>(
this Func<T1, T2, TResult> func, T1 arg1)
=> arg2 => func(arg1, arg2);

var multiply = (int a, int b) => a * b;
var double2 = multiply.Partial(2);
Console.WriteLine(double2(10)); // 20

C# 函数式库

用途
LanguageExt 完整的函数式编程库,提供 Option、Either、Task<Option>、LINQ 扩展、不可变集合等
Optional 轻量级的 Option 类型
LaYumba.Functional 面向 C# 开发者的函数式编程库
FSharp.Core 可与 F# 互操作,使用 F# 函数式类型
1
2
3
4
5
6
7
8
// LanguageExt 示例
using LanguageExt;

Option<int> maybeInt = Some(42);
var mapped = maybeInt.Map(x => x * 2); // Some(84)
var result = from x in maybeInt
from y in Some(100)
select x + y; // Some(142)

不可变集合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
using System.Collections.Immutable;

// 创建
var list = ImmutableList<int>.Empty.Add(1).Add(2).Add(3);
var dict = ImmutableDictionary<string, int>.Empty
.Add("one", 1)
.Add("two", 2);

// 修改产生新实例(结构共享,O(log n))
var newList = list.Remove(1);
Console.WriteLine(list.Count); // 3(原对象不变)
Console.WriteLine(newList.Count); // 2

// 构建器模式(批量操作时使用)
var builder = ImmutableList<int>.Empty.ToBuilder();
builder.AddRange(Enumerable.Range(1, 1000));
var immutable = builder.ToImmutable();

总结:函数式编程在 C# 中的实践应当遵循”汲取精华”的原则——采用不可变数据(record)、纯函数、模式匹配、高阶函数和 LINQ 等 FP 特性来提升代码的安全性、可测试性和组合性。但不必追求纯函数式风格到极端:在 IO、状态管理等场景中拥抱命令式写法,将”纯函数核心 + 命令式边界”作为实用的折衷策略。

ByteFisher
分享编程技术 · 记录钓鱼乐趣
扫码关注
▸ 扫码关注 ◂
分享: