大家都知道我平日寫 .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 to support LINQ operation.


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 參數有誤,要看一下你的程式寫法。

Post a comment