LINQ to Tree - 查询树状结构的通用技术






4.93/5 (105投票s)
本文介绍了一种将LINQ查询应用于树状结构的通用方法。利用T4模板进行代码生成,构建了LINQ to VisualTree (WPF)、LINQ to WinForms和LINQ to FileSystem API。
目录
概述
本文以LINQ to XML为例,探讨了如何将LINQ应用于树状数据。构建了一种通用的树查询方法,通过实现简单的适配器类,可以使任何树结构“兼容”LINQ。这一思路通过T4模板的代码生成进一步扩展,并提供了LINQ to VisualTree (WPF / Silverlight)、LINQ to WinForms和LINQ to Filesystem的示例。

引言
C#编程语言中我最喜欢的功能之一就是LINQ。作为程序员,我们所做的大部分工作都是编写代码来过滤、操作和转换数据。LINQ带来了丰富的功能,让您能够使用更具表现力和简洁的语言来完成这些任务。它显然在语言本身的发展中发挥了重要作用,带来了扩展方法、yield和Lambda表达式。
当我开始学习WPF时,我认为将LINQ应用于可视化树来查询用户界面(UI)的状态可能很有用。对于不熟悉WPF的各位,可视化树是用于构建UI的元素、边框、网格等的层次结构。这些通常在XAML中定义,即XML,XML本身也是一种层次结构。问题在于,LINQ依赖于IEnumerable
接口,该接口用于定义一维项目数组。如何用它来查询WPF可视化树这样的层次结构呢?
快速搜索Google后,我偶然发现了Peter McGrattan的LINQ to Visual Tree实现,它只是将树展平成一维结构。在其他地方,我也找到了David Jade的一个更通用的实现,可以用来展平任何树以应用LINQ查询。然而,在展平树时,您会丢失可能与您的查询相关的重要信息。它们都不是我想要的解决方案!
总之,我将这个问题搁置了一段时间,直到最近开始了一个新项目,我们在处理大量XML数据,自然而然地,我们转向了LINQ to XML...
LINQ to XML 简介
本节简要介绍了LINQ to XML API及其与XPath的关系。如果您已经是LINQ to XML方面的专家,请随时跳过,直接进入重点!
LINQ to XML API提供了查询已加载到内存中的XML文档的方法,这些文档以XDocument
(或简称X-Dom)的形式存在。内存中的XML文档由节点构成树状结构。在每个节点上,都有LINQ方法可用于搜索树,以查找与当前节点(上下文)具有某种关系的子节点、同级节点或节点,这些节点匹配各种条件。
下面提供了一个基于以下XML实例文档的简单示例,该示例查询以下XML:

以下查询将查找所有颜色子元素值为“Red”的“product”元素。

查询的每个部分匹配的节点在上面的XML文档中已高亮显示。
查询的第一部分从文档的根开始,调用XDocument.Descendants
方法查找文档中名称为order的任何元素,这些元素是树中的后代(直接或间接)。
查询的第二部分匹配order元素的所有product后代;然而,有趣的事情从这里开始。虽然上面查询中的第一个Descendants
方法定义在XDocument
上,但紧随其后的第二个Descendants
方法则不是。此方法定义在IEnumerable<XElement>
上,它是通过调用前面Descendant
查询返回的集合中每个XElement
上的Elements
方法的结果。
LINQ to XML为单个XElement
(或XDocument
)上的每个常规查询方法都定义了相应的'IEnumerable' 扩展方法。正是这些方法赋予了LINQ to XML真正的强大功能,让查询能够“爬行”整个文档。
查询的最后一部分过滤product元素。它查找颜色元素,这是一个直接的后代,并找到值为Red的元素。
LINQ to XML API强大且具有表现力,但这些匹配同级、祖先、后代元素的查询方法是如何实现的呢?为此,LINQ to XML开发人员借鉴了现有的XML查询技术,即XPath。上面LINQ中的查询等效的XPath查询是:
//order//product[colour='Red']
XPath允许您通过组合多个表达式和谓词来创建查询,每个表达式都作用于应用前面表达式的结果节点集。这与上面描述的LINQ to XML执行类似。
使用XPath,在XML文档的特定节点上,您可以选择查询多个不同的“轴”,每个轴定义一组与当前上下文节点相关的节点。这允许您定义向不同方向遍历文档的查询。下面总结了LINQ和XPath共有的轴:
LINQ to XML | XPath | 图示 |
后代 | 后代 (//) | ![]() |
祖先 | 祖先 | ![]() |
元素 | 子节点 (/) | ![]() |
同级前元素 | preceding-sibling | ![]() |
同级后元素 | following-sibling | ![]() |
注意:在上面的图示中,以红色高亮显示的编码是当前上下文。以绿色高亮显示的节点是给定轴的成员。此外,XPath为上面给出的每个轴都有一个对应的“or-self”轴,它包括上下文节点;这些也在LINQ to XML中作为“AndSelf”方法存在。
LINQ to Tree 的通用实现
LINQ to XML借鉴了XPath定义的轴来查询XML树。为什么不使用这些相同的轴来查询其他树状结构呢?
问题在于,没有定义树结构的通用接口;例如,WPF可视化树和文件系统API具有非常不同的接口。解决此问题的常用方法是应用Gang of Four的适配器模式,即定义您真正想要的接口,然后“适配”现有类,使其实现该接口,通常通过将其“包装”在另一个类中来实现。
为了遍历树结构,在每个节点上,您只需要导航到父节点或子节点的方法。以下通用接口提供了遍历类型为“T
”的对象树的方法:
/// <summary>
/// Defines an adapter that must be implemented in order to use the LinqToTree
/// extension methods
/// </summary>
/// <typeparam name="T"></typeparam>
public interface ILinqToTree<T>
{
/// <summary>
/// Obtains all the children of the Item.
/// </summary>
/// <returns></returns>
IEnumerable<ILinqToTree<T>> Children();
/// <summary>
/// The parent of the Item.
/// </summary>
ILinqToTree<T> Parent { get; }
/// <summary>
/// The item being adapted.
/// </summary>
T Item { get; }
}
此接口允许我们为类型为“T
”的特定节点的各个轴创建扩展方法。
/// <summary>
/// Returns a collection of descendant elements.
/// </summary>
public static IEnumerable<ILinqToTree<T>>
Descendants<T>(this ILinqToTree<T> adapter)
{
foreach (var child in adapter.Children())
{
yield return child;
foreach (var grandChild in child.Descendants())
{
yield return grandChild;
}
}
}
/// <summary>
/// Returns a collection of ancestor elements.
/// </summary>
public static IEnumerable<ILinqToTree<T>>
Ancestors<T>(this ILinqToTree<T> adapter)
{
var parent = adapter.Parent;
while (parent != null)
{
yield return parent;
parent = parent.Parent;
}
}
/// <summary>
/// Returns a collection of child elements.
/// </summary>
public static IEnumerable<ILinqToTree<T>>
Elements<T>(this ILinqToTree<T> adapter)
{
foreach (var child in adapter.Children())
{
yield return child;
}
}
(我省略了“AndSelf”的实现;这些非常简单;如果您有兴趣,请下载本文的示例代码。)
然而,正如我们在上面的LINQ to XML部分所看到的,这些方法使我们能够从一个节点导航到其相关轴内的节点;然而,正是IEnumerable
扩展方法提供了LINQ to XML的真正强大功能,使您的查询能够“爬行”整个文档。
上面各轴方法对应的IEnumerable
版本如下:
/// <summary>
/// Applies the given function to each of the items in the supplied
/// IEnumerable.
/// </summary>
private static IEnumerable<ILinqToTree<T>>
DrillDown<T>(this IEnumerable<ILinqToTree<T>> items,
Func<ILinqToTree<T>, IEnumerable<ILinqToTree<T>>> function)
{
foreach (var item in items)
{
foreach (ILinqToTree<T> itemChild in function(item))
{
yield return itemChild;
}
}
}
/// <summary>
/// Returns a collection of descendant elements.
/// </summary>
public static IEnumerable<ILinqToTree<T>>
Descendants<T>(this IEnumerable<ILinqToTree<T>> items)
{
return items.DrillDown(i => i.Descendants());
}
/// <summary>
/// Returns a collection of ancestor elements.
/// </summary>
public static IEnumerable<ILinqToTree<T>>
Ancestors<T>(this IEnumerable<ILinqToTree<T>> items)
{
return items.DrillDown(i => i.Ancestors());
}
/// <summary>
/// Returns a collection of child elements.
/// </summary>
public static IEnumerable<ILinqToTree<T>>
Elements<T>(this IEnumerable<ILinqToTree<T>> items)
{
return items.DrillDown(i => i.Elements());
}
(同样,“AndSelf”的实现已被省略。)
在上面的代码中,您可以清楚地看到作用于单个节点的方法与其执行相同功能的IEnumerable
等效方法之间的关系。在上面的代码中,它们都通过私有的DrillDown
方法实现,该方法将给定函数应用于集合中的每个项,并产生结果。
可以通过实现ILinqToTree
接口来包装任何树结构,从而使用此API进行查询。为了实际了解ILinqToTree
接口的用法,我们将创建一个适配器,以便查询WPF可视化树。
/// <summary>
/// An adapter for DependencyObject which implements ILinqToTree in
/// order to allow Linq queries on the visual tree
/// </summary>
public class VisualTreeAdapter : ILinqToTree<DependencyObject>
{
private DependencyObject _item;
public VisualTreeAdapter(DependencyObject item)
{
_item = item;
}
public IEnumerable<ILinqToTree<DependencyObject>> Children()
{
int childrenCount = VisualTreeHelper.GetChildrenCount(_item);
for (int i = 0; i < childrenCount; i++)
{
yield return new VisualTreeAdapter(VisualTreeHelper.GetChild(_item, i));
}
}
public ILinqToTree<DependencyObject> Parent
{
get
{
return new VisualTreeAdapter(VisualTreeHelper.GetParent(_item));
}
}
public DependencyObject Item
{
get
{
return _item;
}
}
}
可视化树由DependencyObject
实例组成,并且可以通过VisualTreeHelper
确定它们之间的关系。有了上面的适配器,我们现在就可以将LINQ查询应用于可视化树了。以下是一个简单示例,其中查询了具有以下XAML标记的Window
:
<Window x:Class="LinqToTreeWPF.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300"
Width="300" ContentRendered="Window_ContentRendered">
<Grid x:Name="GridOne" Background="White">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBox x:Name="TextBoxOne"
Text="One" Grid.Row="0" />
<StackPanel x:Name="StackPanelOne" Grid.Row="1">
<TextBox x:Name="TextBoxTwo" Text="Two" />
<Grid x:Name="GridTwo">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBox x:Name="TextBoxThree"
Text="Three" Grid.Row="0" />
<StackPanel x:Name="StackPanelTwo" Grid.Row="1">
<TextBox x:Name="TextBoxFour" Text="Four"/>
<RadioButton>Radio</RadioButton>
</StackPanel>
</Grid>
<ListBox>
<ListBoxItem>Foo</ListBoxItem>
<ListBoxItem>Bar</ListBoxItem>
</ListBox>
</StackPanel>
</Grid>
</Window>
以下示例演示了针对可视化树执行的几个查询。每个示例都以LINQ查询语法和扩展方法(或流畅)语法提供:
// get all the TextBox's which have a Grid as direct parent
var itemsFluent = new VisualTreeAdapter(this).Descendants()
.Where(i => i.Parent is Grid)
.Where(i => i.Item is TextBox)
.Select(i => i.Item);
var itemsQuery = from v in new VisualTreeAdapter(this).Descendants()
where v.Parent is Grid && v.Item is TextBox
select v.Item;
// get all the StackPanels that are within another StackPanel visual tree
var items2Fluent = new VisualTreeAdapter(this).Descendants()
.Where(i => i.Item is StackPanel)
.Descendants()
.Where(i => i.Item is StackPanel)
.Select(i => i.Item);
var items2Query = from i in
(from v in new VisualTreeAdapter(this).Descendants()
where v.Item is StackPanel
select v).Descendants()
where i.Item is StackPanel
select i.Item;
这些查询中的每一个都遵循相似的模式。首先,可视化树的根被包装在VisualTreeAdapter
(实现ILinqToTree
)中;然后是查询本身;最后,使用Select
子句解包满足我们查询标准的每个项。
在我看来,对树结构的查询通常在流畅/扩展方法语法中更具可读性,查询语法中所需的子选择会不必要地冗长。然而,在少数情况下,查询语法或两者的混合提供了最易读的查询,我们稍后将看到。
虽然这种机制运行良好,但也有一些不太令人满意的地方。首先,添加适配器意味着我们不断地包装和解包树的节点。第二个问题稍微微妙一些。如果能为按类型过滤轴的常见任务添加扩展方法会很好,例如,允许我们找到类型为“TextBox
”的后代。以下应该能满足要求:
/// <summary>
/// Returns a collection of descendant elements.
/// </summary>
public static IEnumerable<ILinqToTree<T>>
Descendants<T, K>(this ILinqToTree<T> adapter)
{
return adapter.Descendants().Where(i => i.Item is K);
}
上面的方法确实产生了期望的结果,通过给定类型K
过滤后代节点。然而,虽然现有方法有一个可以由编译器推断的单个类型参数,但上面的方法有两个类型参数,并且推断不再发生。这意味着我们不仅必须指定要搜索的类型,还要指定正在查询的类型,如下所示:
// find all descendant text boxes
var textBoxes = new VisualTreeAdapter(this).Descendants<DependencyObject, TextBox>();
如果有人能建议一个没有这个问题的扩展方法,我很想知道!请在下方留言...
LINQ to Tree API 的代码生成
为了消除包装/解包树节点的需要,扩展方法需要定义在节点本身上。问题是,如果我们在特定的节点类型上定义扩展方法,这意味着LINQ to Tree API必须为每个节点/树类型手动构建。我找到的解决方案是“内部化”适配器的使用,通过T4模板生成LINQ to Tree扩展方法。
T4模板内置于Visual Studio中,并通过C#和简单的模板标记提供代码文件生成机制。有关编写T4模板的介绍,请尝试CodeProject上的这篇文章。解决方案如下:有一个T4模板文件,其中包含我们简化的ILinqToTree
接口(不需要用于解包的Item
属性或工厂方法),以及LINQ to Tree扩展方法及其IEnumerable
等效方法。此代码包含在一个T4模板方法中,该方法接受参数,这些参数详细说明了正在为其生成API的类型(例如,DependencyObject
、DirectoryInfo
)以及为此类型实现IlinqToTree
接口的类。
这是模板(注意:在此代码片段中,我仅包含后代轴;其他轴及其“AndSelf”对应项都包含在此文章相关的代码中)
<#@ template language="C#" #>
using System.Linq;
using System.Collections.Generic;
using System;
<#+
private void GenerateLinqMethods(string typeName,
string adapterType, string targetNamespace)
{
#>
namespace <#=targetNamespace#>
{
/// <summary>
/// Defines an interface that must be implemented
/// to generate the LinqToTree methods
/// </summary>
/// <typeparam name="T"></typeparam>
public interface ILinqTree<T>
{
IEnumerable<T> Children();
T Parent { get; }
}
public static class TreeExtensions
{
/// <summary>
/// Returns a collection of descendant elements.
/// </summary>
public static IEnumerable<<#=typeName#>>
Descendants(this <#=typeName#> item)
{
ILinqTree<<#=typeName#>> adapter =
new <#=adapterType#>(item);
foreach (var child in adapter.Children())
{
yield return child;
foreach (var grandChild in child.Descendants())
{
yield return grandChild;
}
}
}
/// <summary>
/// Returns a collection of descendant elements which match the given type.
/// </summary>
public static IEnumerable<<#=typeName#>>
Descendants<T>(this <#=typeName#> item)
{
return item.Descendants().Where(i => i is T).Cast<<#=typeName#>>();
}
}
public static class EnumerableTreeExtensions
{
/// <summary>
/// Applies the given function to each of the items in the supplied
/// IEnumerable.
/// </summary>
private static IEnumerable<<#=typeName#>>
DrillDown(this IEnumerable<<#=typeName#>> items,
Func<<#=typeName#>, IEnumerable<<#=typeName#>>> function)
{
foreach(var item in items)
{
foreach(var itemChild in function(item))
{
yield return itemChild;
}
}
}
/// <summary>
/// Applies the given function to each of the items in the supplied
/// IEnumerable, which match the given type.
/// </summary>
public static IEnumerable<<#=typeName#>>
DrillDown<T>(this IEnumerable<<#=typeName#>> items,
Func<<#=typeName#>, IEnumerable<<#=typeName#>>> function)
where T : <#=typeName#>
{
foreach(var item in items)
{
foreach(var itemChild in function(item))
{
if (itemChild is T)
{
yield return (T)itemChild;
}
}
}
}
/// <summary>
/// Returns a collection of descendant elements.
/// </summary>
public static IEnumerable<<#=typeName#>>
Descendants(this IEnumerable<<#=typeName#>> items)
{
return items.DrillDown(i => i.Descendants());
}
/// <summary>
/// Returns a collection of descendant elements which match the given type.
/// </summary>
public static IEnumerable<<#=typeName#>>
Descendants<T>(this IEnumerable<<#=typeName#>> items)
where T : <#=typeName#>
{
return items.DrillDown<T>(i => i.Descendants());
}
}
}<#+
}
#>
模板中的代码与前面各节中的代码非常相似。主要区别在于ILinqToTree
适配器的使用现在被限定在每个扩展方法内,以便LINQ to Tree API直接作用于类型本身。这消除了对“常规”方法上的泛型类型参数的需求,允许我们使用单个类型参数来搜索集合以查找特定类型。
LINQ to VisualTree
为了生成LINQ to Visual Tree API,我们首先创建一个ILinqTree
实现:
/// <summary>
/// Adapts a DependencyObject to provide methods required for generate
/// a Linq To Tree API
/// </summary>
public class VisualTreeAdapter : ILinqTree<DependencyObject>
{
private DependencyObject _item;
public VisualTreeAdapter(DependencyObject item)
{
_item = item;
}
public IEnumerable<DependencyObject> Children()
{
int childrenCount = VisualTreeHelper.GetChildrenCount(_item);
for (int i = 0; i < childrenCount; i++)
{
yield return VisualTreeHelper.GetChild(_item, i);
}
}
public DependencyObject Parent
{
get
{
return VisualTreeHelper.GetParent(_item);
}
}
}
然后,执行T4模板,使用所需的类型:
<#@ template language="C#v3.5" #>
<#@ include file="..\LinqToTreeCodeGen.tt" #>
<#
string typeName = "System.Windows.DependencyObject";
string adapterType = "LinqToVisualTree.VisualTreeAdapter";
string targetNamespace = "LinqToVisualTree";
GenerateLinqMethods(typeName, adapterType, targetNamespace);
#>
通过这几行代码,T4模板生成了一个约400行的API,它允许您查询树,利用前面描述的所有XPath轴。例如,生成的Descendants
方法如下所示:
/// <summary>
/// Returns a collection of descendant elements.
/// </summary>
public static IEnumerable<DependencyObject> Descendants(this DependencyObject item)
{
ILinqTree<DependencyObject> adapter = new VisualTreeAdapter(item);
foreach (var child in adapter.Children())
{
yield return child;
foreach (var grandChild in child.Descendants())
{
yield return grandChild;
}
}
}
我将不会在这篇文章中重现整个LINQ to Visual Tree API,代码本身真的不那么有趣!(您可以在本文附带的代码中找到完整的API。)更有趣的是,我们现在拥有一个通用的LINQ树结构API,只需几行代码即可针对特定树类型生成API。在探索其在其他一些树状结构中的应用之前,让我们回顾一下我们之前应用于可视化树的查询。使用新API,查询更加简洁:
// get all the TextBox's which have a Grid as direct parent
var itemsFluent = this.Descendants<TextBox>()
.Where(i => i.Ancestors().FirstOrDefault() is Grid);
var itemsQuery = from v in this.Descendants<TextBox>()
where v.Ancestors().FirstOrDefault() is Grid
select v;
// get all the StackPanels that are within another StackPanel visual tree
var items2Fluent = this.Descendants<StackPanel>()
.Descendants<StackPanel>();
var items2Query = from i in
(from v in this.Descendants<StackPanel>()
select v).Descendants<StackPanel>()
select i;
LINQ to Windows Forms
另一个常见的树结构是Windows Forms应用程序中的分层控件结构。这个结构对于WinForms开发人员来说并不总是那么明显,因为它不像WPF/Silverlight中的可视化树结构那样重要,在WPF/Silverlight中,属性继承由树结构支持。然而,您可以通过切换到“文档大纲”视图看到UI确实是一个树:

创建我们的LINQ to Windows Forms API同样只是创建一个ILinqToTree
适配器来处理Control
:
/// <summary>
/// Adapts a Control to provide methods required for generate
/// a Linq To Tree API
/// </summary>
class WindowsFormsTreeAdapter : ILinqTree
{
private Control _item;
public WindowsFormsTreeAdapter(Control item)
{
_item = item;
}
public IEnumerable Children()
{
foreach (var item in _item.Controls)
{
yield return (Control)item;
}
}
public Control Parent
{
get
{
return _item.Parent;
}
}
}
并且,创建利用我们前面描述的API生成模板的T4模板:
<#@ template language="C#v3.5" #>
<#@ include file="..\LinqToTreeCodeGen.tt" #>
<#
string typeName = "System.Windows.Forms.Control";
string adapterType = "LinqToWindowsForms.WindowsFormsTreeAdapter";
string targetNamespace = "LinqToWindowsForms";
GenerateLinqMethods(typeName, adapterType, targetNamespace);
#>
这里有两个简单的示例,使用了这个生成的API:
// find any buttons that are within GroupBoxes
var buttons = this.Descendants<Button>()
.Where(i => i.Ancestors<GroupBox>().Any());
// find all visible textboxes
var textBox = this.Descendants<TextBox>()
.Cast<TextBox>()
.Where(t => t.Visible);
LINQ to Filesystem
我想您现在应该知道怎么做了……
以下是适配DirectoryInfo
类型的类的ILinqToTree
实现:
/// <summary>
/// Adapts a DirectoryInfo to provide methods required for generate
/// a Linq To Tree API
/// </summary>
class FileSystemTreeAdapter : ILinqTree<DirectoryInfo>
{
private DirectoryInfo _dir;
public FileSystemTreeAdapter(DirectoryInfo dir)
{
_dir = dir;
}
public IEnumerable<DirectoryInfo> Children()
{
DirectoryInfo[] dirs = null;
try
{
dirs = _dir.GetDirectories();
}
catch (Exception)
{ }
if (dirs == null)
{
yield break;
}
else
{
foreach (var item in dirs)
yield return item;
}
}
public DirectoryInfo Parent
{
get
{
return _dir.Parent;
}
}
}
我还向DirectoryInfo
添加了另一个扩展方法来适配返回数组的“GetFiles
”方法,使其更易于LINQ使用。对于熟悉XPath的各位来说,在这个上下文中,文件可以看作是XML文档中的属性,并构成另一个可以查询的轴。
public static class DirectoryInfoExtensions
{
/// <summary>
/// An enumeration of files within a directory.
/// </summary>
/// <param name="dir"></param>
/// <returns></returns>
public static IEnumerable<FileInfo> Files(this DirectoryInfo dir)
{
return dir.GetFiles().AsEnumerable();
}
}
这里有几个使用此API的示例:
我个人在硬盘上有很多Visual Studio项目,它们是我许多想法的成果,有些好有些坏,但都缺乏组织。我倾向于将较好的项目保存在Subversion (SVN) 中。以下查询查找了我添加到SVN的所有项目:
var dir = new DirectoryInfo(@"C:\Projects");
// find all directories that contain projects checked into SVN
var dirs = dir.Descendants()
.Where(d => d.Elements().Any(i => i.Name == ".svn"))
.Where(d => !d.Ancestors().FirstOrDefault().Elements().Any(i => i.Name == ".svn"));
foreach (var d in dirs)
{
Debug.WriteLine(d.FullName);
}
查询中的第一个where
子句查找包含“.svn”子目录的任何目录。第二个where
子句匹配那些其直接父目录本身不包含“.svn”目录的目录(当您将项目添加到SVN时,所有文件夹都会添加一个.svn文件夹)。
这是另一个有趣的示例,它提供了查询和流畅语法有趣的混合:
// find all 'bin' directories that contain XML files, and output
// the number of XML files they contain.
var dirsWithXML =
from d in dir.Descendants().Where(d => d.Name == "bin").Descendants()
let xmlCount = d.Files().Where(i => i.Extension == ".xml").Count()
where xmlCount > 0
select new{ Directory = d, XmlCount = xmlCount};
foreach (var d in dirsWithXML)
{
Debug.WriteLine(d.Directory + " [" + d.XmlCount + "]");
}
查询的第一部分,“in
”子句,查找所有bin目录并选择它们的所有后代目录;“let
”子句将每个目录中XML文件的数量赋值给变量“xmlCount
”;where
子句选择计数大于0的目录;最后,selects
子句创建一个匿名类型,包含目录和XML文件数量。输出看起来像这样:
...
C:\Projects\SLF\Source\Facades\SLF.BitFactoryFacade\bin\Debug [1]
C:\Projects\SLF\Source\Facades\SLF.BitFactoryFacade\bin\Release [3]
C:\Projects\SLF\Source\Facades\SLF.EntLib20Facade\bin\Debug [3]
C:\Projects\SLF\Source\Facades\SLF.EntLib20Facade\bin\Release [4]
C:\Projects\SLF\Source\Facades\SLF.EntLib41Facade\bin\Debug [2]
C:\Projects\SLF\Source\Facades\SLF.EntLib41Facade\bin\Release [3]
...
结论和最后一个示例
我将用最后一个例子来结束这篇文章,纯粹为了好玩。回到前面LINQ to VisualTree实现中使用的示例XAML。以下单行查询:
string tree = this.DescendantsAndSelf().Aggregate("",
(bc, n) => bc + n.Ancestors().Aggregate("",
(ac, m) => (m.ElementsAfterSelf().Any() ? "| " : " ") + ac,
ac => ac + (n.ElementsAfterSelf().Any() ? "+-" : "\\-")) +
n.GetType().Name + "\n");
输出树结构的ASCII表示:
\-Window1
\-Border
\-AdornerDecorator
+-ContentPresenter
| \-Grid
| +-TextBox
| | \-ClassicBorderDecorator
| | \-ScrollViewer
| | \-Grid
| | +-Rectangle
| | +-ScrollContentPresenter
| | | +-TextBoxView
| | | | \-DrawingVisual
| | | \-AdornerLayer
| | +-ScrollBar
| | \-ScrollBar
| \-StackPanel
| +-TextBox
| | \-ClassicBorderDecorator
| | \-ScrollViewer
| | \-Grid
| | +-Rectangle
| | +-ScrollContentPresenter
| | | +-TextBoxView
| | | | \-DrawingVisual
| | | \-AdornerLayer
| | +-ScrollBar
| | \-ScrollBar
| +-Grid
| | +-TextBox
| | | \-ClassicBorderDecorator
| | | \-ScrollViewer
| | | \-Grid
| | | +-Rectangle
| | | +-ScrollContentPresenter
| | | | +-TextBoxView
| | | | | \-DrawingVisual
| | | | \-AdornerLayer
| | | +-ScrollBar
| | | \-ScrollBar
| | \-StackPanel
| | +-TextBox
| | | \-ClassicBorderDecorator
| | | \-ScrollViewer
| | | \-Grid
| | | +-Rectangle
| | | +-ScrollContentPresenter
| | | | +-TextBoxView
| | | | | \-DrawingVisual
| | | | \-AdornerLayer
| | | +-ScrollBar
| | | \-ScrollBar
| | \-RadioButton
| | \-BulletDecorator
| | +-ClassicBorderDecorator
| | | \-Ellipse
| | \-ContentPresenter
| | \-TextBlock
| \-ListBox
| \-ClassicBorderDecorator
| \-ScrollViewer
| \-Grid
| +-Rectangle
| +-ScrollContentPresenter
| | +-ItemsPresenter
| | | \-VirtualizingStackPanel
| | | +-ListBoxItem
| | | | \-Border
| | | | \-ContentPresenter
| | | | \-TextBlock
| | | \-ListBoxItem
| | | \-Border
| | | \-ContentPresenter
| | | \-TextBlock
| | \-AdornerLayer
| +-ScrollBar
| \-ScrollBar
\-AdornerLayer
当然,同样的查询可以应用于本文中生成的任何LINQ to Tree API。
总之,本文通过实现一个简单的适配器接口和使用T4模板进行代码生成,演示了如何为任何树状结构生成LINQ API。希望您觉得这篇文章很有趣,并可能学到了一些关于LINQ to XML API或XPath轴的新知识。
如果您能将此技术应用于本文未涵盖的其他树结构,我很乐意听您分享,请在下方留言。
尽情享用!
历史
- 2010年3月3日
- 文章更新,
ILinqToTree
接口基于William Kempf的反馈进行了修改
- 文章更新,
- 2010年3月2日
- 文章首次发布