第三章 纯函数与副作用

纯函数

纯函数:

  • 输出确定(相同输入->相同输出)

  • 没有副作用

  • c#实现: immutability, readonly, const, static, [Pure]

示例

public static List<string> GetTitlesOfGenre(List<Book> books, string genre)
{
return books
.Where(b => b.Genre == genre)
.Select(b => b.Title)
.ToList();
}

纯函数的优点

  • 容易测试(不需要模拟环境,只要给输入就行)
  • 可复用性高
  • 容易调试(有问题只有可能是逻辑问题)

示例

private static Dictionary<TowerType, double> _damageModifiers = new()
{
{TowerType.Cannon, 0.8}, // Takes 20% less damage from cannon towers
{TowerType.Laser, 0.9} // Takes 10% less damage from laser towers
};

// 非纯函数
public double CalculateDamageFromTower(Tower tower)
{
return tower.BaseDamage * _damageModifiers[tower.Type];
}

//纯函数
public double CalculateDamageFromTower(Tower tower,
Dictionary<TowerType, double> damageModifiers)
{
return tower.BaseDamage * damageModifiers[tower.Type];
}

副作用

常见副作用例子:

  • 修改全局或静态变量
  • 改变函数参数的原始值
  • 执行输入/输出操作
  • 抛出异常

副作用后果:

  • 行为不再单一
  • 更难调试
  • 并发问题: 如果多个线程同时修改共享状态…….

控制与减少副作用的方法

  1. Immutability

    • 使用 record和 with​
    • 使用readonlyconst
  2. 隔离副作用

    无法避免副作用时,关键在于将它们隔离开来。例如,如果一个函数必须写入文件,那它应该只做这件事。而所有其他逻辑应尽可能放在纯函数中处理。

    这样做的目的是让副作用变得可控、可见且易于管理

[Prue]

[Pure]

  • 声明性的标签,用来指示某个函数是纯的
  • Pure 特性主要用于代码契约和静态分析工具
  • 运行时和编译器并不会强制函数保持纯净,Pure 也不会改变方法本身的行为。

使用 Pure 属性时的注意事项:

  • 不强制函数纯净:你即使标记了,也可能写出带副作用的代码。
  • 不能用于 void 方法:纯函数必须有返回值。
  • 不影响运行时行为:它只是一个标记,不会改变函数在执行时的实际表现

为什么要使用 Pure 属性?

  • 可读性
  • 支持工具检测:一些静态分析工具(比如 Code Contracts)可以利用该属性发现潜在问题。
  • 潜在优化:虽然 C# 编译器目前不对纯函数做特殊优化,但在其他语言或未来的场景中,这类标记可以被编译器用于进一步优化。

Problems & Solves