C# F#函数式编程思想 C#如何借鉴F#的优点

来源:这里教程网 时间:2026-02-21 17:40:47 作者:

record
替代传统
class
声明不可变数据模型

F# 默认鼓励不可变性,而 C# 从 9.0 开始引入

record
类型,正是对这一思想的直接响应。它自动生成值语义比较(
Equals
/
GetHashCode
)、
ToString
和非破坏性修改(
with
表达式),省去大量样板代码。

实操建议:

优先用
record
表示 DTO、领域事件、配置快照等只读数据载体,而非仅当“看起来简单”时才用
避免在
record
中声明可变字段或属性 setter —— 这会削弱其语义一致性,也容易让团队误以为它是“轻量 class”
record struct
在高频小对象场景(如点坐标、颜色)下能减少 GC 压力,但要注意它不支持继承和虚方法

switch
表达式替代
if-else if
链处理多态数据

F# 的模式匹配天然支持类型、结构、常量组合判断;C# 8+ 的

switch
表达式虽未达 F# 级别,但已足够覆盖多数“根据类型/值分支”的场景,且强制穷尽(配合
not null
when
可逼近)。

常见错误现象:仍用嵌套

if
判断
obj is TypeA a && a.Status == X
,导致逻辑分散、遗漏分支、难以测试。

实操建议:

object
或基类变量做运行时类型分发时,优先写
switch (obj)
+
case TypeA a when a.Status == X:
搭配
sealed
层级的继承体系(如
abstract record
+ 具体
record
子类),能让编译器提示未覆盖的 case
避免在
switch
表达式中混用语句块(
{}
)和表达式 —— 统一返回值类型更利于推导和重构

ValueTask
+
async
风格封装纯计算逻辑

F# 的异步工作流(

async { ... }
)本质是描述计算步骤,不绑定线程;C# 的
async
方法默认调度到
ThreadPool
,但若函数只是 CPU-bound 转换(如 JSON 解析后映射为 record),用
Task.Run
反而增加开销。

实操建议:

对纯函数式转换(输入确定 → 输出确定,无副作用),直接写同步方法;需要“假装异步”以适配接口时,用
ValueTask.FromResult(result)
,而非
Task.FromResult
Task.Run
警惕
async void
和未 await 的
async
调用 —— 它们破坏了函数式“可组合性”,也让错误传播不可控
在 LINQ 链中混合异步操作(如
SelectAsync
)时,明确区分“数据流”与“控制流”,避免把
IEnumerable<t></t>
IAsyncEnumerable<t></t>
混用

Option<t></t>
类型模拟 F# 的
option
,但必须配合命名约束

C# 没有原生

option
,但可用开源库(如
LanguageExt
)或手写
Option<t></t>
。问题在于:开发者常把它当“更安全的 nullable”,却忽略其核心价值——显式声明“这个值可能不存在”,并强制调用方处理两种情况。

容易踩的坑:

Option<t></t>
当作
T?
的替代品,在方法签名里隐藏了“空可能性”,导致调用方仍用
.Value
强解包
未禁用隐式转换(如
default(T)
None
),让
null
偷渡进逻辑链
在 EF Core 查询中滥用
Option<t></t>
,触发客户端求值(
ClientEval
)甚至运行时异常

关键不是加一个类型,而是让每个

Some
/
None
分支都有对应语义动作。比如
user.Email.ToOption().Map(EmailService.Send)
if (user.Email != null) EmailService.Send(user.Email)
更难被跳过。

相关推荐