小技巧 - 對只支援 foreach 的集合執行 LINQ 動作
6 |
大家都知道我平日寫 .NET 程式早已「無 LINQ 不歡」,上癮程度直逼「無 LINQ 吾寧死」 (LINQ or Die) 的地步。(延伸閱讀:好 LINQ,不用嗎?)
但是,有一些集合型別偏偏只能 foreach,沒法 Select()、ToArray()。
原因出在這些集合型別只有實作非泛型版的 IEnumerable 介面,沒有實作 IEnumerate<T> 介面。foreach 只需 IEnumerable 就能運作, 但 LINQ 適用有實作 IEnumerable<T> 的型別。
底下列舉一些我常遇到只能 foreach 不能套 LINQ 的集合型別:MatchCollection、NameValueCollection、Array。 如程式範例,過去我只會乖乖另外宣告 List<T> 配合 foreach 做轉換:
static void Test1()
{
var matches =
Regex.Matches("S1 S2 T1 S3 T2 S4", "T[0-9]");
//想得到 string[] { "T1","T2" }
//matches 是 MatchCollection,沒有 Select() 跟 ToArray()
var list = new List<string>();
foreach (Match m in matches)
{
list.Add(m.Value);
}
Console.WriteLine(string.Join(",", list.ToArray()));
var nvc = HttpUtility.ParseQueryString("a=1&b=2&c=3");
//想得到 "a:1,b:2,c:3"
//nvc 為NameValueCollection,無法使用 Select()
list = new List<string>();
foreach (var key in nvc.AllKeys)
{
list.Add($"{key}:{nvc[key]}");
}
Console.WriteLine($"{string.Join(",", list.ToArray())}");
var values = Enum.GetValues(typeof(Stages));
//想得到 int[] { 100,301,402,104 }
//values 為 Array,無法使用 ToArray()
var t = new List<int>();
foreach (int v in values)
{
t.Add(v);
}
Console.WriteLine(string.Join(",", t.ToArray()));
}
雖然只多寫幾行程式,無法征服總留下一絲遺憾。 最近再遇上類似情況,忽然一股熱血來襲,覺得應該找出解決方案。 這才發現答案簡單到不行: IEnumerable 內建 OfType<T>() 跟 Cast<T>() 擴充方法, 可以將 IEnumerable 轉成 IEnumerable<T>。 OfType<T> 與 Cast<T> 的差別在於 OfType 過濾集合元素型別只傳回吻合 T 者,Cast 則是強轉型(轉型失敗會拋出例外)。
廢話不多說,直接看示範:
static void Test2()
{
var matches =
Regex.Matches("S1 S2 T1 S3 T2 S4", "T[0-9]");
//想得到 string[] { "T1","T2" }
Console.WriteLine(
string.Join(",", matches.OfType<Match>().Select(o => o.Value).ToArray()));
var nvc = HttpUtility.ParseQueryString("a=1&b=2&c=3");
//想得到 "{ a:1,b:2,c:3 }"
//NameValueCollection IEnumerator 傳回內部型別,無法直接轉型,改從Key下手
var ary = nvc.Keys.OfType<string>().Select(o => $"{o}:{nvc[o]}").ToArray();
Console.WriteLine($"{{{string.Join(",", ary)}}}");
var values = Enum.GetValues(typeof(Stages));
//想得到 int[] { 100,301,402,104 }
//Array IEnumerator 傳回 Stages
///方法一,用OfType<Stages>()再轉int
Console.WriteLine(string.Join(",",
values.OfType<Stages>().Select(o => (int)o).ToArray()));
///方法二,直接強轉型,用 Cast<T>
Console.WriteLine(string.Join(",", values.Cast<int>()));
}
學會新技巧,以後可以更盡興 LINQ 了,讚!
Some common collections implement only IEnumerable, so they support foreach only. This article demonstrates how to convert them to IEnumerable
Comments
# by 夕照
完全沒注意到可以這樣用!我還自己寫IEnumerable的擴充方法真是浪費時間...
# by ken chen
感謝黑大教學~省了好多時間...XD
# by ByTIM
剛去實際試一下,還真的可以,感謝教學!
# by #
太實用了
# by toadfee
請教個問題 我有個地址的正規表達式是陣列型態,一個檔案是客戶檔 正規表達式,檔案如下,以逗號分隔,陣列的第二欄是表示式,第一欄是6碼郵遞區號 "112017","臺北市\w*一心路\w*" "116019","臺北市\w*一壽街\w*" "112055","臺北市\w*一德街\w*" "112028","臺北市\w*七星街\w*" "100013","臺北市\w*八德路1段\w*" "111004","臺北市\w*力行街\w*" "112057","臺北市\w*三合街1段\w*" ////////////////////////////////////////////////////////////// 客戶資料檔 如下,以分號分隔 XXXX494177;隱藏欄位;111;北市士林區中山北路6段BLA BLA 巷XX號1樓;000646blabla;1090XXX;60XX;2;1090XXX;109-0000XXX 然後我利用 linq 把客戶的第2欄放在陣列 customer[2] (其實是第三欄),再去跟正規表示式裡哪一狗票陣列值比對,編譯器說 match 不支援 string[] 轉換成 string 。 請指導一下,我才剛寫 c#。另外需不需要把原始碼放上
# by Jeffrey
to toadfee,由描述沒看出問題點,可能是 Regex 參數有誤,要看一下你的程式寫法。