先看一個簡單的類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 );
有了這兩個擴展,相信能滿足大部分“樹”的遍歷,為了使用方便還可以進行一些重載。
另外,“樹”的遍歷有 深度優先 和 廣度優先,這裡只提一下,就不再一一給出示例了。