一、高阶函数
高阶函数浅显的来说:某个函数中运用了函数作为参数,如许的函数就称为高阶函数。依据如许的定义,.net中大批运用的LINQ表达式,Where,Select,SelectMany,First等要领都属于高阶函数,那末我们在自身写代码的时刻什么时刻会用到这类设想?
举例:设想一个盘算物业费的函数,var fee=square*price, 而面积(square)依据物业性子的差别,盘算体式格局也差别。民用室庐,贸易室庐等须要乘以差别的系数,依据如许的需求我们试着设想下面的函数:
民用室庐面积:
public Func<int,int,decimal> SquareForCivil() { return (width,hight)=>width*hight; }
贸易室庐面积:
public Func<int, int, decimal> SquareForBusiness() { return (width, hight) => width * hight*1.2m; }
这些函数都有合营的署名:Func<int,int,decimal>,所以我们能够应用这个函数署名设想出盘算物业费的函数:
public decimal PropertyFee(decimal price,int width,int hight, Func<int, int, decimal> square) { return price*square(width, hight); }
是不是是很easy,写个测试看看
[Test] public void Should_calculate_propertyFee_for_two_area() { //Arrange var calculator = new PropertyFeeCalculator(); //Act var feeForBusiness= calculator.PropertyFee(2m,2, 2, calculator.SquareForBusiness()); var feeForCivil = calculator.PropertyFee(1m, 2, 2, calculator.SquareForCivil()); //Assert feeForBusiness.Should().Be(9.6m); feeForCivil.Should().Be(4m); }
二、惰性求值
C#在实行历程运用严厉求值战略,所谓严厉求值是指参数在传递给函数之前求值。这个诠释是不是是照样有点不够清晰?我们看个场景:有一个使命须要实行,请求当前内存运用率小于80%,而且上一步盘算的效果<100,满足这个前提才实行该使命。
我们能够很快写出相符这个请求的C#代码:
public double MemoryUtilization() { //盘算如今内存运用率 var pcInfo = new ComputerInfo(); var usedMem = pcInfo.TotalPhysicalMemory - pcInfo.AvailablePhysicalMemory; return (double)(usedMem / Convert.ToDecimal(pcInfo.TotalPhysicalMemory)); } public int BigCalculatationForFirstStep() { //第一步运算 System.Threading.Thread.Sleep(TimeSpan.FromSeconds(2)); Console.WriteLine("big calulation"); FirstStepExecuted = true; return 10; } public void NextStep(double memoryUtilization,int firstStepDistance) { //下一步运算 if(memoryUtilization<0.8&&firstStepDistance<100) { Console.WriteLine("Next step"); } }
在实行NextStep的时刻须要传入内存运用率和第一步(函数BigCalculatationForFirstStep)的盘算效果,如代码所示,第一步操纵是一个很费时的运算,然则由于C#的严厉求值战略,关于语句if(memoryUtilization<0.8&&firstStepDistance<100)来说,纵然内存运用率已大于80%了,第一步操纵还得实行,很显然,假如内存运用率大于80%,值firstStepDistance已不主要了,完全能够不必盘算。
所以惰性求值是指:表达式或许表达式的一部份只需当真正须要它们的效果时才会对它们举行求值。我们尝试用高阶函数来重写这个需求:
public void NextStepWithOrderFunction(Func<double> memoryUtilization,Func<int> firstStep) { if (memoryUtilization() < 0.8 && firstStep() < 100) { Console.WriteLine("Next step"); } }
代码很简单,就是用一个函数表达式来替代函数值,假如if (memoryUtilization() < 0.8..这句不满足,背面的函数也不会实行。微软在.net4.0版本中加入了Lazy<T>类,人人能够在有这类需求的场景下运用这个机制。
三、函数柯里化(Curry)
柯里化也称作部份套用。定义:是把吸收多个参数的函数变换成吸收一个单一参数(最初函数的第一个参数)的函数,而且返回吸收余下的参数且返回效果的新函数的手艺,ps:为何官方诠释这么绕口?
看到如许的定义预计人人也很难邃晓这是这么一回事,所以我们从curry的原理讲起:
写一个两个数相加的函数:
public Func<int, int, int> AddTwoNumber() { return (x, y) => x + y; }
ok, 怎样运用这个函数?
var result= _curringReasoning.AddTwoNumber()(1,2);
1+2=3,挪用很简单。需求升级,我们须要一个函数,这个函数请求输入一个参数(number),算出10+输入的参数(number)的效果。预计有人要说了,这需求上面的代码完全能够完成啊,第一个参数你传入10不就完了么,ok,假如你是如许想的,我也是迫不得已。另有人能够说了,再写一个重载,只需一个参数即可,实际情况是不容许,我们在挪用他人供应的api,没法增加重载。能够看到部份套用的运用场景不是一种很广泛的场景,所以在适宜的场景合营适宜的手艺才是最好的设想,我们来看部份套用的完成:
public Func<int, Func<int, int>> AddTwoNumberCurrying() { Func<int, Func<int, int>> addCurrying = x => y => x + y; return addCurrying; }
表达式x => y => x + y获得的函数署名为Func<int, Func<int, int>>,这个函数署名异常清晰,吸收一个int范例的参数,获得一个Func<int,int>范例的函数。此时假如我们再挪用:
//Act var curringResult = curringReasoning.AddTwoNumberCurrying()(10); var result = curringResult(2); //Assert result.Should().Be(12);
这句话:var curringResult = curringReasoning.AddTwoNumberCurrying()(10); 生成的函数就是只吸收一个参数(number),且能够盘算出10+number的函数。
一样的原理,三个数相加的函数:
public Func<int,int,int,int> AddThreeNumber() { return (x, y, z) => x + y + z; }
部份套用版本:
public Func<int,Func<int,Func<int,int>>> AddThreeNumberCurrying() { Func<int, Func<int, Func<int, int>>> addCurring = x => y => z => x + y + z; return addCurring; }
挪用历程:
[Test] public void Three_number_add_test() { //Arrange var curringReasoning = new CurryingReasoning(); //Act var result1 = curringReasoning.AddThreeNumber()(1, 2, 3); var curringResult = curringReasoning.AddThreeNumberCurrying()(1); var curringResult2 = curringResult(2); var result2 = curringResult2(3); //Assert result1.Should().Be(6); result2.Should().Be(6); }
当函数参数多了以后,手动部份套用愈来愈不容易写,我们能够应用扩大要领自动部份套用:
public static Func<T1, Func<T2, TResult>> Curry<T1, T2, TResult>(this Func<T1, T2, TResult> func) { return x => y => func(x, y); } public static Func<T1, Func<T2, Func<T3, TResult>>> Curry<T1, T2, T3, TResult>(this Func<T1, T2, T3,TResult> func) { return x => y => z=>func(x, y,z); }
一样的原理,Action<>署名的函数也能够自动套用
有了这些扩大要领,运用部份套用的时刻就越发easy了
[Test] public void Should_auto_curry_two_number_add_function() { //Arrange var add = _curringReasoning.AddTwoNumber(); var addCurrying = add.Curry(); //Act var result = addCurrying(1)(2); //Assert result.Should().Be(3); }
好了,部份套用就说到这里,stackoverflow有几篇关于currying运用的场景和定义的文章,人人能够继承相识。
函数式编程另有一些主要的头脑,比方:纯函数的缓存,所为纯函数是指函数的挪用不受外界的影响,雷同的参数挪用获得的值始终是雷同的。尾递归,票据,代码即数据(.net中的表达式树),部份运用,组合函数,这些头脑有的我也仍然在进修中,有的还在思索其最好运用场景,所以不再总结,假如哪天领会了其头脑会补充。
四、设想案例
末了我照样想设想一个场景,把高阶函数,lambda表达式,泛型要领连系在一起,我之所以设想如许的例子是由于如今许多的框架,开源的项目都有相似的写法,也恰是由于种种手艺和头脑连系在一起,才有了极富有表达力而且异常文雅的代码。
需求:设想一个单词查找器,该查找器能够查找某个传入的model的某些字段是不是包括某个单词,由于差别的model具有差别的字段,所以该查找须要设置,而且能够充分应用vs的智能提醒。
这个功用实在就两个要领:
private readonly List<Func<string, bool>> _conditions; public WordFinder<TModel> Find<TProperty>(Func<TModel,TProperty> expression) { Func<string, bool> searchCondition = word => expression(_model).ToString().Split(' ').Contains(word); _conditions.Add(searchCondition); return this; } public bool Execute(string wordList) { return _conditions.Any(x=>x(wordList)); }
运用:
[Test] public void Should_find_a_word() { //Arrange var article = new Article() { Title = "this is a title", Content = "this is content", Comment = "this is comment", Author = "this is author" }; //Act var result = Finder.For(article) .Find(x => x.Title) .Find(x => x.Content) .Find(x => x.Comment) .Find(x => x.Author) .Execute( "content"); //Assert result.Should().Be(true); }
该案例自身不具有实用性,然则人人能够看到,恰是种种手艺的综合运用才设想出极具语义的api, 假如函数参数改成Expression<Func<TModel,TProperty>> 范例,我们还能够读取到细致的属性称号等信息。
以上就是细致引见C#函数式编程的示例代码的细致内容,更多请关注ki4网别的相干文章!