相信不少人都據說過函數式編程,提到函數式程序設計,腦海裏涌現出來更多的是Lisp、Haskell等語言,而C#,彷佛咱們並不把它當成函數式語言,其實,函數式程序設計並非只針對某個特定的程序設計語言,而C#,也正一步步使用函數式豐富本身的語言結構,幫助人們更好的實現指望的結果。程序員
函數式程序設計算法
函數式程序設計把重點放在函數的應用上,函數式程序設計人員以函數爲基本模塊來創建新函數,這並非說沒有其餘語言的成分,而是說函數是程序體系建立的主要構造。編程
引用透明(Referential transparency)是函數式程序設計領域中的一個重要思想。一個引用透明的函數的返回值只取決於傳遞給它的參數的值。這正好與指令程序設計的基本思想相反。在指令程序設計中,程序的狀態一般會影響函數的返回值。引用透明的函數的數學意義僅存在於函數式程序設計中,這樣的函數稱爲純函數,沒有反作用。ide
函數式程序設計屬於一種定向思惟。若是咱們願意按某種方式去思考,則它能夠給咱們提供有趣的解決方案或者至少思考的源頭,它們都與當前程序設計的許多實際問題有關。模塊化
C#沒法作到像Lisp、Haskell或同屬於.NET平臺的F#那樣很容易實現函數式程序設計,這點咱們必須認可,但從各方面來說,用C#實現函數式程序設計確實是有意義的。函數式編程
C#函數式程序設計基礎之函數與方法函數
因爲C#的函數只能出如今類中,所以它們一般被稱爲方法。方法能夠接受若干個參數,而且能夠有一個返回值。ui
與許多面向對象語言同樣,C#類中的方法能夠是實例方法,也能夠是類方法。而在純函數式程序設計中,沒有類,也沒有類的實例——固然,有不少方法保存數據,但一般不是用類來保存數據,它們老是在許多方面表現出不一樣。this
在面向對象環境中,全部其餘元素只能出如今類和對象的內部(對象是類實例的另外一個說法);而在函數式程序設計中,全部其餘元素都出如今函數內部。有些數據保存在函數的局部變量中,就像C#那樣定義在方法內部的變量,但這並非保存數據最理想的方法。spa
F#把類級別的成員當成全局成員,同時因爲獲得特殊語法的支持,程序員不須要考慮實際發生的「轉換」過程,遺憾的是,在C#中沒法實現這一點,可是解決方法是同樣的。
爲了調用全局級的函數(或者任何其餘做用域的函數),必須在類內建立類級別的成員。這些成員要用static關鍵字。因爲它們都封裝在類中,所以類中的成員有不一樣的可見度。大多數函數式設計環境都有不一樣的封裝級別——如模塊級或命名空間級——所以除了C#中一些比較複雜的語法外,實際上二者沒有多大的區別。
有些函數式語言使用頂級函數或者容許導入模塊或命名空間,這樣就不須要函數調用的修飾符:
DoSomething "string paramers"
在C#中,這樣的調用老是須要一個修飾符,即類名,除非這個函數出如今同一個類的內部:
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表達式中的主體=>運算符右側的部分。能夠採起若干方法進一步簡化代碼。首先, 能夠省略參數類型,由於編譯器能夠根據委託類型的聲明語句推斷出參數的類型:
1 BubbleSorter.IsAGeaterThanBDelegate compareInt2 = 2 (a, b) => { return ((int)a) > ((int)b); };
其次,因爲函數除了返回一個值外不執行任何操做,所以能夠把函數體轉換爲表達式體,而且能夠利用隱式返回:
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#函數式程序設計基礎之引用透明
在指令式程序設計中,這些模塊的基本做用是防止代碼重複,把代碼分解成更容易管理的函數級模塊。指令式程序設計的最大問題之一是隨着時間的推移,模塊會變得愈來愈大。因爲指令式程序設計把重點放在執行序列上,所以函數和方法的引用老是不透明的。
引用透明:表達式能夠用表達式的值取代而不會影響程序,也就是不會影響使用此替換操做的算法的最終結果。