搜尋
首頁後端開發C#.Net教程詳細介紹C#函數式程式設計的範例程式碼

  提起函數式編程,大家一定想到的是語法高度靈活且動態的LISP,Haskell這樣古老的函數式語言,往近了說ruby,javascript,F#也是函數式程式設計的流行語言。然而自從.net支援了lambda表達式,C#雖然作為一種指令式程式設計語言,在函數式程式設計方面也毫不遜色。我們在使用c#編寫程式碼的過程中,有意無意的都會使用高階函數,組合函數,純函數快取等思想,連表達式樹這樣的idea也來自函數式程式設計思想。所以接下來我們把常用的函數式程式設計場景做個總結,有利於我們在程式設計過程中靈活應用這些技術,拓展我們的設計思路和提升程式碼品質。

#   一、高階函數

  高階函數通俗的來講:在某個函數中使用了函數作為參數,這樣的函數就稱為高階函數。根據這樣的定義,.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,所以我們可以利用這個函式簽章設計出計算物件費的函式:

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%,而上一個步驟計算的結果

  我們可以很快寫出符合這個要求的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

  所以惰性求值是指:表達式或表達式的一部分只有當真正需要它們的結果時才會對它們進行求值。我們嘗試用高階函數來重寫這個需求:

public void NextStepWithOrderFunction(Func<double> memoryUtilization,Func<int> firstStep)
{
    if (memoryUtilization() < 0.8 && firstStep() < 100)
    {
        Console.WriteLine("Next step");
    }
}

程式碼很簡單,就是用一個函數表達式來代替函數值,如果if (memoryUtilization() 類,大家可以在有這種需求的場景下使用這個機制。   三、函數柯里化(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類型的函數。此時如果我們再來呼叫:

//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(&#39; &#39;).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> 类型,我们还可以读取到具体的属性名称等信息。

以上是詳細介紹C#函數式程式設計的範例程式碼的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
C#.NET開發今天:趨勢和最佳實踐C#.NET開發今天:趨勢和最佳實踐Apr 28, 2025 am 12:25 AM

C#.NET開發的最新動態和最佳實踐包括:1.異步編程提高應用響應性,使用async和await關鍵字簡化非阻塞代碼;2.LINQ提供強大查詢功能,通過延遲執行和表達式樹高效操作數據;3.性能優化建議包括使用異步編程、優化LINQ查詢、合理管理內存、提升代碼可讀性和維護性、以及編寫單元測試。

C#.NET:使用.NET生態系統構建應用程序C#.NET:使用.NET生態系統構建應用程序Apr 27, 2025 am 12:12 AM

如何利用.NET構建應用?使用.NET構建應用可以通過以下步驟實現:1)了解.NET基礎知識,包括C#語言和跨平台開發支持;2)學習核心概念,如.NET生態系統的組件和工作原理;3)掌握基本和高級用法,從簡單控制台應用到復雜的WebAPI和數據庫操作;4)熟悉常見錯誤與調試技巧,如配置和數據庫連接問題;5)應用性能優化與最佳實踐,如異步編程和緩存。

C#作為多功能.NET語言:應用程序和示例C#作為多功能.NET語言:應用程序和示例Apr 26, 2025 am 12:26 AM

C#在企業級應用、遊戲開發、移動應用和Web開發中均有廣泛應用。 1)在企業級應用中,C#常用於ASP.NETCore開發WebAPI。 2)在遊戲開發中,C#與Unity引擎結合,實現角色控制等功能。 3)C#支持多態性和異步編程,提高代碼靈活性和應用性能。

C#.NET用於網絡,桌面和移動開發C#.NET用於網絡,桌面和移動開發Apr 25, 2025 am 12:01 AM

C#和.NET適用於Web、桌面和移動開發。 1)在Web開發中,ASP.NETCore支持跨平台開發。 2)桌面開發使用WPF和WinForms,適用於不同需求。 3)移動開發通過Xamarin實現跨平台應用。

C#.NET生態系統:框架,庫和工具C#.NET生態系統:框架,庫和工具Apr 24, 2025 am 12:02 AM

C#.NET生態系統提供了豐富的框架和庫,幫助開發者高效構建應用。 1.ASP.NETCore用於構建高性能Web應用,2.EntityFrameworkCore用於數據庫操作。通過理解這些工具的使用和最佳實踐,開發者可以提高應用的質量和性能。

將C#.NET應用程序部署到Azure/AWS:逐步指南將C#.NET應用程序部署到Azure/AWS:逐步指南Apr 23, 2025 am 12:06 AM

如何將C#.NET應用部署到Azure或AWS?答案是使用AzureAppService和AWSElasticBeanstalk。 1.在Azure上,使用AzureAppService和AzurePipelines自動化部署。 2.在AWS上,使用AmazonElasticBeanstalk和AWSLambda實現部署和無服務器計算。

C#.NET:強大的編程語言簡介C#.NET:強大的編程語言簡介Apr 22, 2025 am 12:04 AM

C#和.NET的結合為開發者提供了強大的編程環境。 1)C#支持多態性和異步編程,2).NET提供跨平台能力和並發處理機制,這使得它們在桌面、Web和移動應用開發中廣泛應用。

.NET框架與C#:解碼術語.NET框架與C#:解碼術語Apr 21, 2025 am 12:05 AM

.NETFramework是一個軟件框架,C#是一種編程語言。 1..NETFramework提供庫和服務,支持桌面、Web和移動應用開發。 2.C#設計用於.NETFramework,支持現代編程功能。 3..NETFramework通過CLR管理代碼執行,C#代碼編譯成IL後由CLR運行。 4.使用.NETFramework可快速開發應用,C#提供如LINQ的高級功能。 5.常見錯誤包括類型轉換和異步編程死鎖,調試需用VisualStudio工具。

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

EditPlus 中文破解版

EditPlus 中文破解版

體積小,語法高亮,不支援程式碼提示功能

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

Atom編輯器mac版下載

Atom編輯器mac版下載

最受歡迎的的開源編輯器