C# 函数式编程:从语法到架构
\n\n现代 C# 早已超越了纯面向对象语言的范畴。从 3.0 版本(LINQ)开始,一直到最新的 .NET 版本,该语言积极吸收函数式编程(FP)的范式。如果您已经掌握了 lambda 表达式和 LINQ,但希望更深入地探索,那么本文正适合您。我们将剖析高级 FP 概念,这些概念将使您的 C# 代码更可预测、更易测试且更简洁。
\n\n1. 纯函数与副作用处理
\n\n纯函数 是指满足以下条件的函数:
\n- \n
- 对于相同的参数,始终返回相同的结果(引用透明性)。 \n
- 没有副作用(不修改全局状态,不写入文件,不发送 HTTP 请求)。 \n
在 C# 中,纯函数的理想示例是数学运算。问题在于,实际应用程序不能仅由纯函数组成:迟早需要保存数据或发送邮件。解决方案是 将逻辑与副作用分离。
\n\n高级技巧:使用 函子(Functor)和单子(Monad) 来封装副作用。考虑一个简单的 Maybe<T> 示例(类似于 nullable,但更具函数式风格):
public class Maybe<T>\n{\n private readonly T _value;\n public bool HasValue { get; }\n \n private Maybe(T value, bool hasValue) => (_value, HasValue) = (value, hasValue);\n \n public static Maybe<T> Some(T value) => new Maybe<T>(value, true);\n public static Maybe<T> None() => new Maybe<T>(default, false);\n \n // 函子:Select (Map)\n public Maybe<TResult> Select<TResult>(Func<T, TResult> selector) =>\n HasValue ? Maybe<TResult>.Some(selector(_value)) : Maybe<TResult>.None();\n \n // 单子:SelectMany (FlatMap/Bind)\n public Maybe<TResult> SelectMany<TResult>(Func<T, Maybe<TResult>> binder) =>\n HasValue ? binder(_value) : Maybe<TResult>.None();\n}\n\n// 使用示例\nMaybe<int> result = Maybe<int>.Some(10)\n .Select(x => x * 2)\n .SelectMany(x => x > 15 ? Maybe<int>.Some(x) : Maybe<int>.None());\n\n这段代码不会抛出异常,也无需手动检查 null —— 所有处理值缺失的逻辑都封装在单子中。
2. 柯里化与部分应用
\n\n在经典 FP 中,每个函数只接受一个参数。柯里化 是将一个接受多个参数的函数转换为一系列嵌套函数,每个函数只接受一个参数。部分应用 则是固定部分参数。
\n\nC# 没有内置的柯里化,但可以通过闭包轻松实现:
\n\n// 普通函数\nFunc<int, int, int, int> add = (a, b, c) => a + b + c;\n\n// 柯里化版本\nFunc<int, Func<int, Func<int, int>>> curriedAdd = a => b => c => a + b + c;\n\n// 部分应用:固定第一个参数\nFunc<int, Func<int, int>> add5 = curriedAdd(5);\n\n// 使用示例\nint sum = add5(10)(20); // 35\n\n高级场景:通过部分应用实现依赖注入。无需通过类构造函数传递配置或服务,可以在组合阶段将其固定:
\n\npublic class ReportService\n{\n public Func<string, string, string> FormatReport => \n (template, data) => $"[{DateTime.Now}] {template}: {data}";\n}\n\n// 使用部分应用\nvar service = new ReportService();\nvar formatWithTemplate = service.FormatReport("Error"); // 固定模板\nstring errorMessage = formatWithTemplate("Connection failed");