发布于 2026-01-06 4 阅读
0

禅与函数式 C# 现在,我的兄弟们,去拥抱禅吧!

禅宗和函数式 C#

现在,去拥抱禅意吧,我的兄弟们。

编写函数式程序可以带来类似禅宗的体验。函数式 C# 使用简洁、简短的代码块,清晰地展现其意图和内部工作原理,从而解开代码中的各种难题,使代码空间更加简洁

不再有副作用

深吸一口气,屏住呼吸5秒钟,然后呼气。

是时候从功能性角度思考问题了,也是时候告别那些令人讨厌的、毫无益处的副作用了。

替代文字

禅宗准则建议:

避免:不纯的函数

public void UpdatePrice(decimal vat, decimal cost, decimal markup)
{
    this.price = (cost + markup) * (1 + vat);
}
Enter fullscreen mode Exit fullscreen mode

首选:纯函数

public decimal PriceIncludingVat(decimal vat, decimal cost, decimal markup)
{
    return (cost + markup) * (1 + vat);
}

price = PriceIncludingVat(0.2m, 10m, 2m);
Enter fullscreen mode Exit fullscreen mode

调用此函数PriceIncludingVat不会修改现有状态;它会返回新状态。您可以根据需要多次调用此函数,price直到您实际设置当前状态之前,都不会受到影响。

练习将冗长的非纯方法拆分成纯函数。将方法的副作用提取出来,并将它们转化为返回新状态的独立函数。你可能会发现自己正在梳理一些非常棘手的代码——这会让你感到释怀,并让你更接近禅定。

不变性

闭上眼睛。吸气。呼气。拥抱永恒的宁静。

替代文字

你的状态将不再可变。新的不可变对象取代new旧的不可变对象。通过将对象设为不可变,可以确保任何作用域内任何事物都无法更改它们。这完全避免了副作用。要更改不可变对象的属性,唯一的方法是用包含更改的新对象替换该对象。

禅宗准则建议:

避免:可变对象

public class ZenGarden
{
    public int Tranquillity { get; set; } // mutable
    public int Peace { get; set; } // mutable

    public ZenGarden(int tranquillity, int peace)
    {
        Tranquillity = tranquillity;
        Peace = peace;
    }
}
Enter fullscreen mode Exit fullscreen mode

这导致

var garden = new ZenGarden(10, 20);
garden.Peace = -1000;
// or even worse
public int TranquilityAndPeace(ZenGarden garden)
{
    garden.Peace -= 1; // a side effect
    return garden.Tranquillity + garden.Peace;
}
Enter fullscreen mode Exit fullscreen mode

更喜欢

public class ZenGarden
{
    public readonly int Tranquillity;
    public readonly int Peace;

    public ZenGarden(int tranquillity, int peace)
    {
        Tranquillity = tranquillity;
        Peace = peace;
    }
}
Enter fullscreen mode Exit fullscreen mode

这导致

var garden = new ZenGarden(10, 20);
garden = new ZenGarden(10, 21); // peace goes up, because of immutability :]
// pure function
public int TranquilityAndPeace(ZenGarden garden)
{
    var calculatedGarden = new ZenGarden(garden.Tranquillity, garden.Peace - 1);
    return calculatedGarden .Tranquillity + calculatedGarden .Peace;
}
Enter fullscreen mode Exit fullscreen mode
  • 注意,readonly这里使用私有 setter 而不是私有 setter ,以确保即使在自身内部也无法更改这些值class

无需再担心不必要的更改(副作用)——不可变性确保没有任何事物会改变同一状态,也友好地告别了竞态条件线程锁

参考透明度

拥抱清晰的禅意。引用透明的函数可以直接用它被调用时返回的值来替换。无论上下文如何,它对相同的参数都返回相同的值。引用不透明的函数则恰恰相反——通常是因为它们引用了可能会改变的外部值。

引用透明性有助于提高代码的可读性。它能理清那些从作用域外获取值的函数,并限制它们在计算返回值时只能操作自身的参数。

它隔离函数,保护它们免受代码外部更改的影响。

替代文字

禅宗准则建议:

避免:不透明函数

public double PriceOfFood(double price) 
{
    // if the number of people changes, the price of food changes
    // regardless of price changing
    return this.numberOfPeople * price;
}
Enter fullscreen mode Exit fullscreen mode

首选:透明功能

public double PriceOfFood(double price, int numberOfPeople) 
{
    return numberOfPeople * price;
}
Enter fullscreen mode Exit fullscreen mode

高阶函数

C# 完全将函数视为一等公民。让我们把这些函数当作一等公民来对待。C# 将函数视为一等公民。这意味着函数可以作为方法的参数;函数可以赋值给变量;甚至可以作为其他函数的返回值。

将函数作为一等公民,使我们能够将代码拆分成一个个小的构建块,每个构建块都封装了一小段(希望是干净、纯粹且引用透明的)功能。这些小块可以组合使用,实现不同的功能。随着需求的变化,我们可以替换和互换这些小块,而对代码库的影响微乎其微。

替代文字

按需生成功能——不要编写大量代码。让函数像我们所知的运转部件一样运行。

高阶函数接受函数作为参数,或者返回函数。由于 C# 将函数视为一等公民,因此允许我们创建高阶函数。

禅宗准则建议:

避免:不明确的 lambda表达式

var range = Enumerable.Range(-20, 20);
range.Where(i => i % 2 == 0);
range.Where(i => i % 5 != 0);
Enter fullscreen mode Exit fullscreen mode

首选:命名函数

Func<int, bool> isMod(int n) => i => i % n == 0;
range.Where(isMod(true, 2));
range.Where(!isMod(false, 5));
Enter fullscreen mode Exit fullscreen mode

避免:过程式功能调用(如适用)

public class Car {...}

public void ChangeOil(Car car) {...}
public void ChangeTyres(Car car) {...}
public void Refuel(Car car) {...}

public void PitStop(Car car)
{
    ChangeOil(car);
    ChangeTyres(car);
    Refuel(car);
}

PitStop(car);
Enter fullscreen mode Exit fullscreen mode

优先:动态函数调用(如适用)

public class Car {...}

public void ChangeOil(Car car) {...}
public void ChangeTyres(Car car) {...}
public void Refuel(Car car) {...}

public void PitStop(Car car, IEnumerable<Action<Car>> pitstopRoutines)
{
    foreach(var routine in pitstopRoutines)
        routine(car);
}

PitStop(car, new List<Action<Car>>{ChangeOil, ChangeTypes, Refuel});
Enter fullscreen mode Exit fullscreen mode

这种方法PitStop更具可扩展性。它易于扩展,几乎不需要修改。只需将要执行的函数作为参数传递,并将它们视为一等公民,即可实现这一点。

现在,去拥抱禅意吧,我的兄弟们。

替代文字

文章来源:https://dev.to/htissink/zen-and-function-c-3mo3