用慣 LINQ 後不太能忍受回頭用 foreach 處理集合物件,List<T>、IEnumerable<T> 及物件陣列可直接 Where()、Select() ,基本上涵蓋大部分應用情境,但有些時候還是會遇到一些不支援 LINQ 擴充方法的集合物件。這篇筆記將介紹透過簡單轉換讓集合支援 LINQ 的小技巧。(別像我以前傻傻先 var list = new List<T> 再 foreach 跑 list.Add(…) )

舉兩個我遇過的例子。

有字串 A12+A34+B99-C56+A87,打算用 Regex.Matches 挑出 A 起首的數字代碼,用 Select().ToArray() 轉成字串陣列 "12","34","87"。

有 app.config 如下,我想透過 System.Configuration.ConfigurationManager.AppSettings 將 d:SvcIp d:SvcPort 設定用 ToDictionary() 轉成 Dictionary<string, string>。

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <add key="d:SvcIp" value="192.168.1.87" />
    <add key="d:SvcPort" value="80" />
    <add key="Interval" value="5000" />
  </appSettings>
</configuration>

依直覺寫出以下程式碼,但無法編譯,理由是 MatchCollection 跟 NameValueCollection 兩種集合型別不適用 LINQ 擴充方法。

.Select() 是 IEnumerable<TSource> 的擴充方法,而 MatchCollection 只實作了 ICollection 與 IEnumerable 介面,故無法直接使用 Select、Where、ToList 等 LINQ 擴充方法:

.Cast<T>() 是 IEnumerable 的擴充方法,可將 IEnumerable 轉換成 IEnumerable<TResult>,如此即可大方使用 LINQ 方法加工。

NameValueCollection 是另一種案例,它繼承自 NameObjectCollectionBase,NameObjectCollectionBase 雖然實作了 IEnumerable,但其 GetEnumerator() 傳回 IEnumerator 只能取回 Name 字串,充其量 Cast<string> 跟 AllKeys 相比多此一舉,故我們改對 AllKeys(型別為 string[],陣列型別可使用 LINQ 擴充方法)進行 Select 產生 Key Value 物件,再依原本構想進行 Where() 篩選與 ToDictionary() 轉換。

補充一點,appSettings 不接受同一 key 值設定多次,遇重複 key 時以最後一次 value 為準,故 appSettings 設定永遠是一對一。但 NameValueCollections 本身允許同一 key 值對應多組 value,透過 Get("key") 取得 "value1,value2,value3" 以逗號分隔的字串,GetValues("key") 則傳回 "value1", "value2", "value3" 字串陣列,如要轉換成 Dictionary<string, string>需考量此一狀況。

最後再來個練習,DataTable.Rows 的型別為 DataRowCollection,其父類別 InternalDataCollectionBase 實作了 ICollection, IEnumerable,跟 MatchCollection 狀況相同,解法也一樣,Cast<DataRow> 後可使用 Select(),若想套用我很愛的 ForEach() 方法,則用 ToList() 轉型成 List<DataRow> 即可如願~ 〔註: ForEach() 非 IEnumerable<T> 的擴充方法,為 List<T> 的專屬方法〕


Comments

Be the first to post a comment

Post a comment