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

剛去實際試一下,還真的可以,感謝教學!

Post a comment


14 - 10 =