函数式编程

函数式编程

C# 函数式编程到底是啥?—通俗讲解给 .NET 开发者

前段时间接触到C#函数式编程,看完一脸懵逼。“就这?”,这中方式我不天天在用吗?怎么就函数式编程了。我面向对象不更香吗?

其实,函数式编程并不是放弃面向对象(有些情况函数式搞不定的),也不是某种高深的学术技巧,更像是一种风格。先直观感受一波

1
2
3
4
5
6
7
8
9
10
11
var result = new List<int>();
//传统写法
foreach (var x in numbers)
{
if (x % 2 == 0)
result.Add(x * 2);
}
//函数式编程写法
var result = numbers.Where(x => x % 2 == 0)
.Select(x => x * 2)
.ToList();

你看完是不是也觉得,“就这?不就是Linq吗,我都用了八百年了”,没错,很简单,本篇文章只不过是稍微总结下函数式编程的特点,用法等,当技术科普看看吧,当然,初学者就不建议看了,因为有些玩法是需要有一定经验的开发者才能看得懂。

函数式编程特点

不要乱改变量,不要影响函数外面的东西

多用不可变对象(Immutable),如数组最好不要增删,如果必须增删,那就返回一个新的数组,不要影响之前的

把函数/方法当作数据处理器,而不是操作状态的机器,函数也可以作为参数传入和传出,懂委托的小伙伴肯定懂

初识函数式编程

经常碰到的情况是对数组进行增删,所以参数多用ImmutableList<int> numbersList=ImmutableList.Create<int> ( 1, 3, 5, 7 );比如

1
2
3
4
public void AddNumbersToList(ImmutableList<int>inputList)
{
inputList.Add(4);//会产生一个新的ImmutableList<int>
}

还有就是函数给定一个输入要有确定的输出,

1
2
3
4
5
6
7
8
9
//这不好的案例,给定输入,输出不确定。因为有DateTime.Now,最好把DateTime.Now当作参数传进来,同输入则同输出
public DateTime GetCurrentTimeRoundedUpToCustomMinuteInterval(int interval)
{
var currentTime = DateTime.Now;
var minutesSpan = TimeSpan.FromMinutes(interval).Ticks;
return new DateTime((currentTime.Ticks / minutesSpan + 1) * minutesSpan);
}
//改成这样比较好
public DateTime GetCurrentTimeRoundedUpToCustomMinuteInterval(int interval, DateTime startTime)

Func<T,Result>Predicate<T>

函数式编程的一个非常强大的地方就是,多用Func<T,Result>Predicate<T>来作为参数输出和输出

仿照Linq实现一个Linq中的where

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
public static class MyLinq
{
//C#14用法
extension<T>(IEnumerable<T> items)
{
public IEnumerable<T> MyWhere(Predicate<T> predicate)
{
foreach (T item in items)
{
if (predicate(item))
{
yield return item;
}
}
}
public IEnumerable<T> MyTransform(Func<T,T>transformer)
{
foreach (T item in items)
{
yield return transformer(item);
}
}
}
//静态块
extension<T>(IEnumerable<T>)
{
public static IEnumerable<T> MyCombine(IEnumerable<T> first,IEnumerable<T> second){
return first.Concat(second);
}
}
}
1
2
3
4
5
var numbers1 = Enumerable.Range(1, 5);
var numbers2 = Enumerable.Range(1, 5);
var divisibleByFive = numbers1.MyWhere(x => x % 2 == 0).Dump();
var PowersOfThree = numbers1.MyTransform(x => x * x * x).Dump();
MyLinq.MyCombine(numbers1,numbers2).Dump();

image-20251202155430919

可以组合使用numbers1.MyWhere(x=>x%2==0).MyTransform(x =>x*2 )

image-20251202170047883

我用也可以用更高级的用法(高阶函数),返回一个Func<T,T>

1
2
Func<int,int> AddTo(int n)=>i=>i+n;//详细看这个用法
numbers1.MyTransform(AddTo(3)).Dump();//每个+3

image-20251202155539681

Build工厂方法

当类中有很多属性需要初始化时,往往需要大量的构造函数,可以用下面这种工厂方法

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
public sealed class Color
{
public byte Red { get; }
public byte Green { get; }
public byte Blue { get; }
private Color(byte red, byte green, byte blue)
{
Red = red;
Green = green;
Blue = blue;
}
public class Builder
{
private byte _red;
private byte _green;
private byte _blue;
public Builder Red(byte red)
{
_red = red;
return this;
}
public Builder Blue(byte blue)
{
_blue = blue;
return this;
}

public Builder Green(byte green)
{
_green = green;
return this;
}
public Color Create()
{
return new Color(_red, _green, _blue);
}
}
}
var c1 = new Color.Builder().Blue(1).Red(5).Create();
var c2 = new Color.Builder().Blue(5).Create();

image-20251202160931030

管道式写法

前一个输出作为下一个函数的输入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static class MyExtension
{
extension(int candidate)
{
public int AddTo(int adder)
{
return candidate+adder;
}
public int MakeNegative()
{
return candidate*-1;
}
}
}
//使用
int value = 6;
value.AddTo(2).MakeNegative(); //-8

可以直接写一个泛型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static class MyExtension
{
extension<T>(T candidate)
{
public T PerformOperation(Func<T, T> func)
{
return func(candidate);
}
}
}
//使用
int value = 6;
value.PerformOperation(v => v+2).PerformOperation(v =>-2*v ); //-16
"hello".PerformOperation(h => h.Remove(0,1) ).PerformOperation(h => h+"~" );//ello~

还有一种是参数是一个函数,每调用一次减少一个函数,并返回一个新函数

1
2
3
4
5
6
7
8
9
10
11
12
13
public static class MyExtension
{
extension<TReturn1, TReturn2>(Func<TReturn1, TReturn2> func1)
{
public Func<T, TReturn2> Compose<T>(Func<T, TReturn1> func2)
=> x => func1(func2(x));
}
}

Func<int, int> toFourthPower = x => x * x * x * x;
Func<int, int> makeNegative = x => -1 * x;
toFourthPower(makeNegative(5));//传统调用方式
toFourthPower.Compose(makeNegative)(5);//函数式调用方式

在 C# 中,函数式编程就是:用不可变数据 + 纯函数 + LINQ + 函数组合,让代码变得更短、更可靠、更“像数学”。

作者

步步为营

发布于

2025-12-02

更新于

2025-12-02

许可协议