程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> C#函數式程序設計之函數、委托和Lambda表達式

C#函數式程序設計之函數、委托和Lambda表達式

編輯:C#入門知識

相信很多人都聽說過函數式編程,提到函數式程序設計,腦海裡湧現出來更多的是Lisp、Haskell等語言,而C#,似乎我們並不把它當成函數式語言,其實,函數式程序設計並不是只針對某個特定的程序設計語言,而C#,也正一步步使用函數式豐富自己的語言結構,幫助人們更好的實現期望的結果。   函數式程序設計   函數式程序設計把重點放在函數的應用上,函數式程序設計人員以函數為基本模塊來建立新函數,這並不是說沒有其他語言的成分,而是說函數是程序體系創建的主要構造。   引用透明(Referential transparency)是函數式程序設計領域中的一個重要思想。一個引用透明的函數的返回值只取決於傳遞給它的參數的值。這正好與指令程序設計的基本思想相反。在指令程序設計中,程序的狀態通常會影響函數的返回值。引用透明的函數的數學意義僅存在於函數式程序設計中,這樣的函數稱為純函數,沒有副作用。   函數式程序設計屬於一種定向思維。如果我們願意按某種方式去思考,則它可以給我們提供有趣的解決方案或者至少思考的源頭,它們都與當前程序設計的許多實際問題有關。   C#無法做到像Lisp、Haskell或同屬於.NET平台的F#那樣很容易實現函數式程序設計,這點我們必須承認,但從各方面來講,用C#實現函數式程序設計確實是有意義的。   C#函數式程序設計基礎之函數與方法   由於C#的函數只能出現在類中,因此它們通常被稱為方法。方法可以接受若干個參數,並且可以有一個返回值。   與許多面向對象語言一樣,C#類中的方法可以是實例方法,也可以是類方法。而在純函數式程序設計中,沒有類,也沒有類的實例——當然,有很多方法保存數據,但通常不是用類來保存數據,它們總是在許多方面表現出不同。   在面向對象環境中,所有其他元素只能出現在類和對象的內部(對象是類實例的另一個說法);而在函數式程序設計中,所有其他元素都出現在函數內部。有些數據保存在函數的局部變量中,就像C#那樣定義在方法內部的變量,但這並不是保存數據最理想的方法。   F#把類級別的成員當成全局成員,同時由於得到特殊語法的支持,程序員不需要考慮實際發生的“轉換”過程,遺憾的是,在C#中無法實現這一點,但是解決方法是一樣的。   為了調用全局級的函數(或者任何其他作用域的函數),必須在類內創建類級別的成員。這些成員要用static關鍵字。由於它們都封裝在類中,因此類中的成員有不同的可見度。大多數函數式設計環境都有不同的封裝級別——如模塊級或命名空間級——因此除了C#中一些比較復雜的語法外,實際上兩者沒有多大的區別。   有些函數式語言使用頂級函數或者允許導入模塊或命名空間,這樣就不需要函數調用的修飾符:   1 DoSomething "string paramers" 在C#中,這樣的調用總是需要一個修飾符,即類名,除非這個函數出現在同一個類的內部:   1 SomeClass.DoSomething("string paramers"); C#函數式程序設計基礎之重用函數   在計算機程序設計中,重用是一個非常重要的綜合問題。函數並不是可重用性的唯一方法,特別在面向對象程序設計中,很快出現了其他方法。作為C#的一個內置功能,它只支持函數的重載作為函數級模塊化的直接辦法,C#4.0支持命名參數和可選參數,因此重載函數的解析過程變得相當復雜,特別當它與其他相關方法(如在方法調用時進行泛型類型推斷)一起使用時。   下面舉一個重載方法的簡單例子:        1         int Add(int x, int y)  2         {  3             return x + y;  4         }  5   6         int Add(int x, int y,int z)  7         {  8             return Add(x, y) + z;  9         } 10  11         double Add(double x, double y) 12         { 13             return x + y; 14         } 15  16         double Add(double x, double y, double z) 17         { 18             return Add(x, y) + z; 19         }   在這個例子中,我們很清楚地看出為什麼重載與重用有關:它允許程序員創建與原函數類似的新函數,同時盡可能利用原函數已有的功能。   C#函數式程序設計基礎之匿名函數與Lambda表達式   並非所有的函數都重要到需要一個名稱,一般而言,這些函數並不是類級別的函數,它們沒有名稱,這些函數的引用地址保存在變量中,因此只要有這些函數的引用地址就可以調用它們。   從技術上講,匿名函數肯定要受到某些限制。很遺憾的是,其中之一就是它們不可以是泛型,它們也不可以用來實現迭代器。除此之外,匿名函數幾乎可以包括所有做任何“正常”方法可以做的事情。       1 static void AnonymousMethods() 2         { 3             BubbleSorter.IsAGeaterThanBDelegate compareInt = 4                 delegate(object a, object b) 5                 { 6                     return ((int)a) > ((int)b); 7                 }; 8         }   以上是C#2.0的代碼,可以看出,關鍵字delegate委托代替了方法名。參數列表和方法體還是與前面一樣。這個匿名方法也可以改寫成如下形式,這裡用了C#3.0的Lambda表達式語法:     1 BubbleSorter.IsAGeaterThanBDelegate compareInt2 = 2                 (object a, object b) => { return ((int)a) > ((int)b); }; 這段代碼較短,因為少了delegate關鍵字,方法體已經寫成一行格式。Lambda表達式中的主體=>運算符右側的部分。可以采取若干方法進一步簡化代碼。首先, 可以省略參數類型,因為編譯器可以根據委托類型的聲明語句推斷出參數的類型:    View Code 其次,由於函數除了返回一個值外不執行任何操作,因此可以把函數體轉換為表達式體,並且可以利用隱式返回:     BubbleSorter.IsAGeaterThanBDelegate compareInt2 =                 (a, b) =>(int)a) > ((int)b); 表達式體很有用。因為有了它,在函數式程序中本來需要用函數實現的某個操作現在可以簡化為一個表達式。與函數一樣,表達式體也要接受參數並返回一個值。表達式體不可以包含任何與返回值求值無關的代碼(即只要有一個返回值就行,遺憾的是,經常在表達式體中使用沒有返回值的表達式)。   前面的例子如果使用其中一個泛型委托類型,就可以變成如下的形式:     1 Func<object,object,bool> compareInt3= 2             (a, b) => ((int)a) > ((int)b); 這個委托需要接受兩個object類型的參數,返回一個bool值。使用泛型委托類型的另一個好處是,它們的參數類型更容易看明白,因為它們在委托類型中采用顯式聲明,而且編譯器可以為Lambda表達式推斷出它們的類型。   使用Lambda表達式時,有一個細節需要牢記:只有當所有類型都確定後,編譯器才會根據幾個比較復雜的准則進行類型推斷。編譯器並不是總能正確地推斷出類型,因此,如果所有的類型都確定了,編譯器的要求就滿足了:     1 Func<int, int, int> add = 2             (a, b) => a + b; 在這個Lambda表達式中不可以使用var關鍵字,C#中,編譯器必須能夠在聲明的位置推斷出參數的類型,對於下面的語句則無法推斷出參數的類型:     1 var add = 2             (a, b) => a + b; 函數式程序設計語言要求,在所有與類型推斷有關的情形中都需要像這樣的顯式說明。這在某些C#程序員看來是遺憾的。   C#函數式程序設計基礎之擴展方法   擴展方法是靜態類中用特殊方法表示的靜態方法:        1 namespace CompanyWideTools  2 {  3     public static class StringHelper  4     {  5         public static string ConCat(this string[] strings, string separator)  6         {  7             bool first = true;  8             var builder = new StringBuilder();  9             foreach (var s in strings) 10             { 11                 if (!first) 12                     builder.Append(separator); 13                 else 14                     first = false; 15                 builder.Append(s); 16             } 17             return builder.ToString(); 18         } 19     } 20 }   表示Concat是一個擴展方法的標志是在該方法的參數列表中使用this關鍵字。這個關鍵字是C#專有的,用於命令編譯器給這個方法中增加ExtensionMethodAttribute屬性。可以像調用靜態方法那樣調用擴展方法:     1         string[] strings = new[] 2             { 3                 "to","be","or","not","to","be" 4             }; 5  6             Console.WriteLine(StringHelper.ConCat(strings," "));     然而,由於它是擴展方法,因此也可以像下面這樣調用:     1  Console.WriteLine(strings.ConCat("")); 當我們需要充分利用擴展方法的優點時,這種調用方法比較簡單。   每個擴展方法都有一個可擴展的特定類型:第一個參數的類型,即用this標志的那個參數。這個標志只可以用於第一個參數,不可以用於其他參數。擴展方法的第一個參數可以是一個基類類型或者一個接口,甚至可以是System.Object中的對象。擴展方法也可以是泛型的,他們可以擴展泛型類型。   C#函數式程序設計基礎之引用透明   在指令式程序設計中,這些模塊的基本作用是防止代碼重復,把代碼分解成更容易管理的函數級模塊。指令式程序設計的最大問題之一是隨著時間的推移,模塊會變得越來越大。由於指令式程序設計把重點放在執行序列上,因此函數和方法的引用總是不透明的。   引用透明:表達式可以用表達式的值取代而不會影響程序,也就是不會影響使用此替換操作的算法的最終結果

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved