代码改变世界

c#扩展方法奇思妙用高级篇七:“树”通用遍历器

2009-11-09 22:06 by 鹤冲天, ... 阅读, ... 评论, 收藏, 编辑

 我的上一篇随笔《c#扩展方法奇思妙用高级篇六:WinForm 控件选择器》中给出了一个WinForm的选择器,其实质就是一个“树”的遍历器,但这个遍历局限于WinForm的Control类。在数据结构中,“树”的遍历是一个通用算法,那我们为什么不做一个通用的“树”遍历扩展呢?

 

 先看一个简单的类People(将作为测试用的例子):

1     public abstract class People
2     {
3         public bool IsMale { getprivate 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#扩展方法奇思妙用》,敬请关注!