我的上一篇隨筆《c#擴充方法奇思妙用進階篇六:WinForm 控制項選取器》中給出了一個WinForm的選取器,其實質就是一個“樹”的遍曆器,但這個遍曆局限於WinForm的Control類。在資料結構中,“樹”的遍曆是一個通用演算法,那我們為什麼不做一個通用的“樹”遍曆擴充呢?
先看一個簡單的類People(將作為測試用的例子):
1 public abstract class People
2 {
3 public bool IsMale { get; private set; }
4 public abstract IEnumerable<People> Children { get; }
5 }
People類有一個Children屬性,返回該People的所有孩子。People類通過Children屬性最終可形成一個People樹。
“樹”的通用遍曆擴充參照如下:
1 public static IEnumerable<T> GetDescendants<T>(this T root,
2 Func<T, IEnumerable<T>> childSelector, Predicate<T> filter)
3 {
4 foreach (T t in childSelector(root))
5 {
6 if (filter == null || filter(t))
7 yield return t;
8 foreach (T child in GetDescendants((T)t, childSelector, filter))
9 yield return child;
10 }
11 }
使用People類,寫出幾個調用樣本:
1 People people;
2 //
3 //擷取所有子孫
4 var descendants = people.GetDescendants(p => p.Children, null);
5 //擷取所有男性子孫
6 var males = people.GetDescendants(p => p.Children, p => p.IsMale);
當然,還有另外一種情況,只擷取本族人的子孫(子孫中的女性嫁出,不包括她們的子孫),這種情況稍複雜些,本文更側重想法,不再給出範例程式碼(哪們朋友實現了,可發在回複中)。
既然是通用的,我們就將它用在WinForm中作為選取器試試吧:
1 //Form1.cs
2 //擷取本表單所有控制項
3 var controls = (this as Control).GetDescendants(c => c.Controls.Cast<Control>(), null);
4 //擷取所有選中的CheckBox
5 var checkBoxes = (this as Control).GetDescendants(
6 c => c.Controls.Cast<Control>(),
7 c => (c is CheckBox) && (c as CheckBox).Checked
8 )
9 .Cast<CheckBox>();
通用的方法寫起來肯定沒有專用的優雅,用了多處 is/as 和 Cast,主要因為這裡涉及到繼承,而且Control.Controls屬性的類型ControlCollection不是泛型集合。
以上兩個例子比較相似:樹結構中“根”與“子孫”類型相同(或具有相同的基類),WinForm中的TreeView就不同了:TreeView(根)包含多個TreeNode(子孫),每個TreeNode也可包含多個TreeNode(子孫),“根”與“子孫”類型不同(也沒有相同的基類),如:
我們要使用另外一個擴充(要調用上面的擴充方法):
1 public static IEnumerable<T> GetDescendants<TRoot, T>(this TRoot root,
2 Func<TRoot, IEnumerable<T>> rootChildSelector,
3 Func<T, IEnumerable<T>> childSelector, Predicate<T> filter)
4 {
5 foreach (T t in rootChildSelector(root))
6 {
7 if (filter == null || filter(t))
8 yield return t;
9 foreach (T child in GetDescendants(t, childSelector, filter))
10 yield return child;
11 }
12 }
調用代碼如下:
1 //擷取TreeView中所有以“酒”結尾的樹結點
2 var treeViewNode = treeView1.GetDescendants(
3 treeView => treeView.Nodes.Cast<TreeNode>(),
4 treeNode => treeNode.Nodes.Cast<TreeNode>(),
5 treeNode => treeNode.Text.EndsWith("酒")
6 );
有了這兩個擴充,相信能滿足大部分“樹”的遍曆,為了使用方便還可以進行一些重載。
另外,“樹”的遍曆有 深度優先 和 廣度優先,這裡只提一下,就不再一一給出樣本了。
本人系列文章《c#擴充方法奇思妙用》,敬請關注!