zoukankan      html  css  js  c++  java
  • c#扩展方法奇思妙用高级篇七:“树”通用遍历器

     我的上一篇随笔《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#扩展方法奇思妙用》,敬请关注!

  • 相关阅读:
    Qt对文件的删除、复制、移动、可执行文件位置
    qt关于窗口关闭触发函数/信号
    QString.toUtf8().data()的问题 & char *转换到QByteArray注意
    Qt中类型之间的转换
    C++中auto和decltype的区别和功能
    Delphi 系统[11]关键字和保留字 goto、label
    Delphi 系统[10]关键字和保留字 with
    Delphi 系统[9]关键字和保留字 for、to、downto、do、while、repeat、until
    Delphi 系统[8]关键字和保留字 if、then、else、case
    Delphi 系统[7]关键字和保留字 begin、end
  • 原文地址:https://www.cnblogs.com/ldp615/p/1599312.html
Copyright © 2011-2022 走看看