標籤:f# 實用函數編程 函數編程 序列運算式 平面映射
12.3.3.3 在 C# 中使用平面映射
類似於 collect 函數的LINQ 運算子,是 SelectMany,但兩者之間也有差異,因為 LINQ 有不同的要求。而 F# 序列運算式只能使用 collect 函數表示,LINQ 查詢可以使用許多其它運算子,所以,對於序列操作,它們需要不同的方式。
我們再先看一下普通文法,然後,再考慮轉換成使用顯式擴充方法的文法,我們還使用前面的 F# 樣本的資料。有關國家資訊的城市列表中包含了 CityInfo 類的執行個體,有兩個屬性,輸入名字的列表只包含字串。清單 12.13 展示的是我們寫的 LINQ 查詢,尋找輸入城市所屬的國家。
清單 12.13 使用查詢找出輸入城市的國家 (C#)
var q =
from e in entered [1]
from known in cities [2]
where known.City == e | [3]
select string.Format("{0} ({1})", known.City, known.Country); |
這個查詢運算式與我們在前面樣本中所實現的完全相同,它遍曆兩個資料來源(entered [1] 和 cities [2]),我們交叉聯結兩個集合,然後,只產生與使用者輸入城市名,在“已知的城市”列表中相對應的城市記錄;最後,格式化輸出[3]。
在 C# 查詢運算式文法中,我們也可以使用 jion 子句,直接指定兩個資料來源的鍵(在我們的例子中,是值 e 和 known.City 值)。也有一些不同:join 子句可能更有效,但是,幾個 from 子句更靈活。特別是,我們產生的第二個序列可能會根據我們當前看到的第一個序列中的項目。
正如我們剛才所說的,查詢運算式可以轉換為正常的成員調用。在查詢運算式中,第一個 from 子句之後的任何 from 子句,都被轉換成對 SelectMany 的調用。清單 12.14 顯示了由 C# 編譯器執行的轉換。
清單 12.14 查詢轉換成運算子調用 (C#)
var q = entered
.SelectMany(
e=> cities, [1]
(e, known) => new { e, known }) [2]
.Where(tmp => tmp.known.City == tmp.e) |
.Select(tmp => String.Format("{0} ({1})", | 篩選,格式化輸出
tmp.known.City, tmp.known.Country)); |
不像在 F # 中,if 條件被嵌套在兩個 for 迴圈中(平面映射),在 C# 中的操作是沒有嵌套的順序組合。首先是 SelectMany 運算子,實現聯結;篩選和映射,在序列的末尾用 Where 和 Select 執行。
第一個 匿名函式[1],為源列表中的每個項目產生指定集合,這個參數對應於提供給 F# 的 collect 函數的參數值的函數。在查詢中,返回所有已知的城市,所以,操作只執行聯結,而沒有任何篩選,或進一步地處理。第二個參數[2]指定如何根據原始序列的中元素產生結果,由函數所返回的新產生的序列中的元素。在樣本中,我們用包含了兩個項目的匿名型別,這樣,我們就可以在以後的查詢運算子使用它們。
在 F# 中,所有的處理是在篩選和映射(filtering projection,外國人也能這樣並列使用名字了,要麼就應該是平面映射)內部做的,這樣,我們只返回最終的結果。在 C# 中,大多數處理在後面完成,所以,我們需要返回兩個元素,再組合成一個值(使用匿名型別),使它們能在以後訪問。通常,第一個 from 子句指定主查詢源,如果我們添加更多的 from 子句,它們通過 SelectMany 運算子,與原始的資料來源聯結。任何後續的運算子,比如,where 和 select,都只能加到最後,處理聯結好的資料來源。這不同於 F# 轉換,因為在 F# 中,篩選和映射都嵌套在最裡面,調用 Seq.collect。
理解如何轉換並不是那麼重要,但是,下一節我們還是要作一點瞭解。我們將會看到,F# 序列運算式表示更普通的想法,也可以在一定程度上使用 LINQ 查詢來表示;我們已經看到的平面映射將發揮關鍵作用。
12.3.3.3 在 C# 中使用平面映射