linq 递归_自定义递归LINQ扩展

楚权
2023-12-01

linq 递归

What does this article hope to achieve?

本文希望实现什么?

   I’ve been writing software for quite a long time now and I try to identify any repetitive task and determine if it is code that I can isolate and write once and reuse later. Recursion is a pattern that surfaces semi-frequently and always requires a moment of thought and likely a few iterations through the debugger to ensure whatever tree-like structure I’m dealing with is being traversed properly.

我从事软件开发已有很长时间了,我尝试确定任何重复性任务,并确定它是否是我可以隔离并编写一次并在以后重用的代码。 递归是一种半频繁出现的模式,并且总是需要花点时间思考,并且可能需要通过调试器进行几次迭代才能确保正确处理我正在处理的任何树状结构。

  Recursion has always sat in the back of my mind as something that should fall into a reusable pattern library that I can carry in my toolbox but I’ve just never gotten around to it largely because I felt each implementation was significantly unique enough not to lend itself well to a generic pattern. I was flat wrong.

递归一直在我的脑海中回荡,因为它应该属于我可以在工具箱中携带的可重用模式库,但是我从来没有绕过它,主要是因为我觉得每个实现都非常独特,无法借用本身很适合通用模式。 我错了。

   I recently read about writing a simple custom LINQ extension on Sascha Barber’s website in this article. Although this wasn’t a recent article it inspired me to conquer my long standing procrastination concerning a generic recursion pattern. The benefit of a LINQ enabled custom extension that provided generic recursion was obvious. One could chain in the LINQ recursion extension anywhere one was writing a LINQ statement or as a stand-alone LINQ statement itself.

最近,我读到这写在萨沙理发的网站上简单的自定义LINQ扩展的文章 。 尽管这不是最近的文章,但它启发了我克服了对通用递归模式的长期拖延。 LINQ支持的自定义扩展的好处是,它提供了通用的递归。 在编写LINQ语句或作为独立LINQ语句的任何地方,都可以在LINQ递归扩展中进行链接。

   I wanted to write something that would recurse any structure that lent itself to recursion. I came up with the following common test scenarios in my mind that would enable me to prove the extension as sufficiently generic.

我想写一些可以递归任何递归结构的东西。 我想到了以下常见的测试场景,这些场景可以使我证明扩展足够通用。

•      XML

•XML

•      TreeView nodes on a windows form

•Windows窗体上的TreeView节点

•      Windows File System

•Windows文件系统

   Obviously, XML content can be tree like in nature. XML is a group of ever nesting elements that can be easily traversed using recursion. In a name, a TreeView says it all. A TreeView can contain TreeNode visual objects that in turn contain TreeNode child objects to the nth level. The Windows File System should require no explaining. We’re all familiar that directories can contain other directories and/or files and so forth.

显然,XML内容本质上可以像树一样。 XML是一组永远嵌套的元素,可以使用递归轻松遍历。 就像一个TreeView所说的那样。 TreeView可以包含TreeNode可视对象,而这些可视对象又包含第n级的TreeNode子对象。 Windows文件系统不需要任何解释。 我们都熟悉目录可以包含其他目录和/或文件等等。

   So, as a proof of concept, I want my Recurse LINQ extension method to handle all of these scenarios hopefully in an elegant manner but certainly as code that I know “just works” that I don’t have to write from scratch every time a situation for recursion presents itself.

因此,作为概念验证,我希望我的Recurse LINQ扩展方法能够以一种优雅的方式处理所有这些情况,但是可以肯定的是,我知道“可以正常工作”的代码不必每次都从头开始编写。递归的情况出现了。

Without any further ado let’s take a look at what I came up with as far as the Recurse LINQ extension and explain the code in further detail.

事不宜迟,让我们看一下我对Recurse LINQ扩展所提出的想法,并进一步详细解释代码。

using System;
using System.Collections.Generic;
using System.Linq;

namespace Linq.Extensions
{
	public static class LinqExtensions
	{
		public static IEnumerable<T> Recurse<T> 
			(
				this T root, 
				Func<T, IEnumerable<T>> findChildren
			) 
			where T : class
		{
			yield return root;

			foreach (var child in 
				findChildren(root)
					.SelectMany(node => Recurse(node, findChildren))
					.TakeWhile(child => child != null))
			{
				yield return child;
			}

			// SAME CODE AS ABOVE IN A MORE VERBOSE FASHION
			//foreach (var node in findChildren(root))
			//{
			//    foreach (var child in Recurse(node, findChildren))
			//    {
			//        if (child == null)
			//            yield break;
					
			//        yield return child;
			//    }
			//}
		}
	}
}

  I wanted a least common denominator approach to allow for the greatest flexibility in the Recurse method. The bare minimum information we need to provide a generic recursion method is: a starting point of recursion, or the “root”, as well as how each item determines its child objects. While this methodology allows for the greatest flexibility it requires a little forethought into what exactly you want to recurse and how exactly to fetch the children of what you’re recursing.

我希望使用最不常用的分母方法,以便在递归方法中获得最大的灵活性。 我们需要提供通用递归方法的最低限度的最低信息是:递归的起点或“根”,以及每个项目如何确定其子对象。 尽管此方法可以提供最大的灵活性,但仍需要对要递归的确切内容以及要递归的子对象的确切获取方式进行一些预想。

What makes this method a LINQ extension?

是什么使该方法成为LINQ扩展?

   Paraphrasing Sasha’s article, all we need to conform to a LINQ extension is to provide…

解读Sasha的文章,我们需要符合LINQ扩展的所有内容是:

1) An IEnumerable<T> return
1)IEnumerable <T>返回
2) Conform to any extension method signature which takes a first parameter of “this”. In our case we will be using the T generic type of our return. The “this” parameter is what denotes this method as an “extension method”. There are countless well written articles concerning the writing of extension methods that you can peruse should you be unfamiliar with the concept.
2)符合任何采用第一个参数“ this”的扩展方法签名。 在我们的例子中,我们将使用T泛型类型的返回值。 “ this”参数表示将此方法称为“扩展方法”。 如果您不熟悉扩展方法,那么会有很多写得很好的文章,涉及扩展方法的编写。
3) Be a static method in a static class. The latter is not required it’s just a good practice of organization. I plan on adding new LINQ extensions in future articles so for now we just have the single static class and method.
3)是静态类中的静态方法。 不需要后者,这只是组织的一种良好做法。 我计划在以后的文章中添加新的LINQ扩展,因此目前我们只有单个静态类和方法。

That’s all we have to do for our method to be available to us as a LINQ extension which resolves the implied problem in the first part of the article title. If you’re interested in writing an extension that does something other than recursion just follow those basic guidelines and away you go.

这就是我们要做的所有事情,我们的方法才能作为LINQ扩展提供给我们,该扩展可以解决文章标题第一部分中的隐式问题。 如果您有兴趣编写除递归以外的其他功能的扩展程序,只需遵循这些基本准则即可。

   I do recommend implementing the “yield return” methodology when writing methods that return an IEnumerable<T> in C# code where possible. There are many articles out there describing the inherent benefits of yield return that you should check out if this approach isn’t clear to you. In fact, in the case of recursion, it’s imperative as I’ll explain in more detail later.

我确实建议在可能的情况下编写在C#代码中返回IEnumerable <T>的方法时实现“收益回报”方法。 那里有很多文章描述了收益率收益的内在好处,如果您不清楚这种方法,则应该检查一下。 实际上,在递归的情况下,这一点势在必行,我将在后面详细解释。

What makes this method implement generic recursion?

是什么使该方法实现了通用递归?

   The recursive nature of the method call lies in the Recurse method calling itself, passing on each child found in the provided “findChildren” method as the new root for recursion. This is the beauty and elegance found in the nature of recursion. We keep calling our method, nesting inside of ourselves until we discover and return all children to the nth level. This should be immediately obvious to anyone whom has written any type of recursive function in their programming career.

方法调用的递归性质在于调用自身的Recurse方法,并将在提供的“ findChildren”方法中找到的每个子级作为递归的新根传递。 这是递归本质中发现的美丽和优雅。 我们一直在调用我们的方法,嵌套在我们自己内部,直到发现所有子级并将其返回第n级。 对于任何在编程生涯中编写过任何类型的递归函数的人来说,这应该立即显而易见。

   A common problem with recursion is that you can build a huge stack on the memory heap as you recurse further and further into the structure tree. This problem is neatly avoided with the C# yield return methodology. The very nature of the yield return keeps us from pushing an unknown amount of data onto the memory stack but rather take a single item, as needed, while we recursively traverse our generic tree structure.

递归的一个常见问题是,随着您进一步递归到结构树中,您可以在内存堆上构建巨大的堆栈。 使用C#收益率返回方法可以很好地避免此问题。 收益回报的本质使我们无法将未知数量的数据推入内存堆栈,而是根据需要采用单个项目,同时递归地遍历我们的通用树结构。

   That being said, I don’t recommend porting this to VB.Net. While possible, the aforementioned problem cannot be averted to my knowledge but I’m certainly open to a VB.Net solution that could handle this situation effectively.

话虽如此,我不建议将此移植到VB.Net。 虽然有可能,但我无法避免上述问题,但是我当然愿意接受可以有效处理这种情况的VB.Net解决方案。

   Please note the commented code which is provided for an easier read. The implemented and commented code is semantically the same and the latter is only provided for clarity. Referencing the code that is commented out we can see this is a very simple method indeed. We simply iterate each child of our root object by calling the provided findChildren method. Our findChildren method takes in a parameter of type T (our generic type) and provides an IEnumerable<T> return. See the pattern here? To effectively recurse our generic tree we have to be able to plug back in results that match the results of our Recurse method itself. On each iteration of the root node we Recurse our child node itself providing the exact same method to find children.

请注意提供的注释代码,以便于阅读。 已实现和注释的代码在语义上是相同的,仅为了清楚起见,仅提供后者。 引用注释掉的代码,我们可以看到这确实是一个非常简单的方法。 我们只需调用提供的findChildren方法来迭代根对象的每个子对象。 我们的findChildren方法接受类型T(我们的通用类型)的参数,并提供IEnumerable <T>返回。 在这里看到图案吗? 为了有效地递归通用树,我们必须能够插入与Recurse方法本身的结果匹配的结果。 在根节点的每次迭代中,我们递归子节点本身,以提供完全相同的方法来找到子节点。

   Please note I could take 1000 images of debugging steps to try and clarify what will make immediate sense to the reader actually stepping though the code in debug mode themselves. If the nature of the above code does not make immediate sense please take the time to step through one of the unit tests at least once to see how things are working not only with the code but with the nature of the C# compiler and the yield return statements themselves. One thing that will jump out at you is when the code is actually called which is when you start to actually iterate the results of the Recurse method. Try stepping into the Recurse method itself and you will see what I mean. The compiler delves into the Recurse method when the first result is actually needed (as in the case of the first element in a foreach loop being required).

请注意,我可以拍摄1000张调试步骤的图像,以尝试弄清对于实际踏入调试模式代码的读者来说立即有意义的事情。 如果上面的代码的性质没有立即意义,请花点时间至少一次通过一个单元测试,以查看事情不仅对代码有效,而且与C#编译器的性质以及收益率有关陈述自己。 当您实际调用代码时,即您开始实际迭代 Recurse方法的结果时,您会想到的一件事。 尝试逐步进入Recurse方法本身,您将明白我的意思。 当实际需要第一个结果时(例如,在foreach循环中需要第一个元素的情况下),编译器会研究Recurse方法。

Testing our generic recursive method

测试我们的通用递归方法

   I have included an example unit test written to test our three scenarios in three simple methods. Let’s take a look at the two easiest scenarios first.

我提供了一个示例单元测试,该单元测试旨在通过​​三种简单方法测试我们的三种情况。 首先让我们看一下两个最简单的场景。

The TestXmlRecursion test method loads an XmlDocument into memory with sample XML content I’ve saved into the resource of the unit test project itself. Nothing special going on there we just have some very basic XML as follows...

TestXmlRecursion测试方法将XmlDocument的示例XML内容加载到内存中,而我已经将其保存到单元测试项目本身的资源中。 没什么特别的,我们只有一些非常基本的XML,如下所示...

<Root>
	<ChildA>
		<SubChildA></SubChildA>
		<SubChildB></SubChildB>
		<SubChildC></SubChildC>
	</ChildA>
	<ChildB>
		<SubChildA></SubChildA>
		<SubChildB></SubChildB>
	</ChildB>
	<ChildC>
		<SubChildA></SubChildA>
	</ChildC>
	<ChildD>
	</ChildD>
</Root>

   Ideally we want to iterate the XML tree as intuitively as possible and output the XML almost as you read it. Without implementing indentation that's exactly what our output will look like as I've written the test.

理想情况下,我们希望尽可能直观地迭代XML树,并在阅读时几乎输出XML。 如果没有实现缩进,那正是我编写测试时输出的样子。

   Our unit test method and supporting methods are as follows...

我们的单元测试方法和支持方法如下...

[TestMethod]
public void TestXmlRecursion()
{
	var xmlDocument = new XmlDocument();
	xmlDocument.LoadXml(Resources.SampleXmlData);

	foreach (var node in xmlDocument.FirstChild.Recurse(EnumerateXmlElements))
	{
		WriteXmlNode(node);
	}
}

private static void WriteXmlNode(XmlNode node)
{
	Debug.WriteLine(node.Name);
}

public IEnumerable<XmlNode> EnumerateXmlElements(XmlNode node)
{
	return node.ChildNodes.Cast<XmlNode>();
}

   Our code couldn't be more simple. After we do the footwork of loading up the XmlDocument we grab the first child and call our LINQ Recurse method providing our custom-written EnumerateXmlElements method to provide our Recurse() LINQ  method with the children it needs for proper processing.

我们的代码再简单不过了。 完成加载XmlDocument的工作后,我们将抓住第一个孩子,并调用LINQ Recurse方法,该方法提供了自定义编写的EnumerateXmlElements方法,以为Recurse()LINQ方法提供正确处理所需的孩子。

   Our EnumerateXmlElements method is very simple just passing back the ChildNodes property of the XmlNode provided. Please note the Cast<XmlNode> call which is a great way to provide a proper IEnumerable<T> of any framework collections that are implemented as a special collection such as an XmlNodeCollection or TreeNodeCollection, etc. I use this all over the place to facilitate LINQ queries built on top of framework or custom collections where appropriate.

我们的EnumerateXmlElements方法非常简单,只需返回提供的XmlNode的ChildNodes属性即可。 请注意Cast <XmlNode>调用,这是为实现为特殊集合(例如XmlNodeCollection或TreeNodeCollection等)的任何框架集合提供正确的IEnumerable <T>的好方法。我在各处使用它来简化LINQ查询建立在适当的框架或自定义集合之上。

   For our simple example we take in an XmlNode object and write out the node.Name property which is the text value of the XML element itself. Although I don't go into attributes or 1000 other XML specific examples here you can easily see that once we have a method taking in an XmlNode parameter returned from our recursive call we have a lot of versatility here on what operations we can perform with our XML elements inside the XmlDocument.

对于我们的简单示例,我们接受一个XmlNode对象,并写出node.Name属性,该属性是XML元素本身的文本值。 尽管我在这里不介绍属性或其他1000个XML特定示例,但您可以轻松地看到,一旦我们有了从递归调用返回的XmlNode参数的方法,我们就可以对我们执行的操作进行多种选择XmlDocument中的XML元素。

  So what does our ouput look like? Exactly, I think, as our intuitive minds would imagine, as a list of XML node element tags with no indentation...

那么我们的输出是什么样的呢? 确实,正如我们的直觉思维所想象的那样,我认为是没有缩进的XML节点元素标签列表...

Root

ChildA

儿童A

SubChildA

子孩子A

SubChildB

子孩子B

SubChildC

子孩子C

ChildB

儿童B

SubChildA

子孩子A

SubChildB

子孩子B

ChildC

儿童C

SubChildA

子孩子A

ChildD

儿童D

   for a bit of fun and to demonstrate proper LINQ extensibility I've included a reversed XML method which simply calls the .Reverse() LINQ extension after our .Recurse() call and, as expected, this is our result...

为了获得一些乐趣并演示LINQ的适当扩展性,我提供了一个反向XML方法,该方法在我们调用.Recurse()之后简单地调用.Reverse()LINQ扩展,这是我们的预期结果。

ChildD

儿童D

SubChildA

子孩子A

ChildC

儿童C

SubChildB

子孩子B

SubChildA

子孩子A

ChildB

儿童B

SubChildC

子孩子C

SubChildB

子孩子B

SubChildA

子孩子A

ChildA

儿童A

Root

  For brevity, I'm omitting a full explanation of the TreeView unit test methodology because it is almost identical in nature to our XML example. One glance at the code provided should prove that evident.

为简洁起见,我省略了TreeView单元测试方法的完整说明,因为它本质上与我们的XML示例相同。 只需看一下所提供的代码就可以证明这一点。

  The file system example is a little different not so much in the call to our LINQ Recurse example but more in the area of "where to start" and "what to provide". Let's take a look...

文件系统示例与LINQ Recurse示例的调用稍有不同,而在“从哪里开始”和“提供什么”方面更大。 让我们来看看...

[TestMethod]
public void TestFileSystemRecursion()
{
	const string rootPath = @"C:\inetpub\";

	foreach (var path in rootPath.Recurse(EnumerateFileSystemEntries))
	{
		WritePath(path);
	}
}

public IEnumerable<string> EnumerateFileSystemEntries(string root)
{
	if (Directory.Exists(root))
	{
		foreach (var fileSystemEntry in Directory.EnumerateFileSystemEntries(root))
		{
			yield return fileSystemEntry;
		}
	}

	yield return null;
}

public static void WritePath(string path)
{
	Debug.WriteLine(path);
}

  So what's different here? Well, here we're taking a string path and enumerating the string via our EnumerateFileSystemEntries method which returns a list of strings. Huh? Really what we're recursing is a string tree in essence which just happens to be a nested list of paths. In our WritePath method we simply output the path of where we are in the file system tree.

那么这里有什么不同? 好吧,这里我们采用字符串路径,并通过我们的EnumerateFileSystemEntries枚举字符串 返回字符串列表的方法。 ?? 实际上,我们要递归的实际上是一棵字符串树,它恰好是路径的嵌套列表。 在我们的WritePath方法中,我们仅输出文件系统树中所处位置的路径。

   You may be thinking something's fishy about this approach and you'd be right. In fact there's nothing wrong with this approach as long as all we're interested in is the path of the folder or file down the tree. What if we wanted to output the creation time of each item though? We would effectively have to read the FileSystemInfo object twice, once inside our recursion and again inside our WritePath method to retrieve a FileSystemInfo object based on the fetched path. This obviously isn't the most streamlined approach to solve our problem in this case. I have included a TestFileSystemRecursionImprovedPerformance unit test method to simulate a work-around for this issue and how to best use the Recurse() extension to our advantage. I'm leaving this out of the article explanation, again for brevity, but it's a good example of how you can do things different ways with such a generic solution.

您可能会认为这种方法有些可疑,并且您是对的。 实际上,只要我们感兴趣的只是文件夹或文件在树上的路径,这种方法就没有问题。 如果我们想输出每个项目的创建时间怎么办? 我们将必须有效地读取两次FileSystemInfo对象,一次是在我们的递归内部,另一次是在我们的WritePath方法内部,以根据提取的路径检索FileSystemInfo对象。 在这种情况下,这显然不是解决我们问题的最简化方法。 我包括了一个TestFileSystemRecursionImp 巡回演出 rmance单元测试方法可模拟解决此问题的方法,以及如何最好地使用Recurse()扩展来发挥我们的优势。 为了简洁起见,我在本文的解释中不再赘述,但这是一个很好的示例,说明了如何使用这种通用解决方案以不同的方式进行操作。

Included below are the the full code samples you can copy and paste into your own solutions.

下面包括完整的代码示例,您可以将它们复制并粘贴到自己的解决方案中。

Custom LINQ Extension Recurse itself

自定义LINQ扩展递归本身

using System;
using System.Collections.Generic;
using System.Linq;

namespace Linq.Extensions
{
	public static class LinqExtensions
	{
		public static IEnumerable<T> Recurse<T> 
			(
				this T root, 
				Func<T, IEnumerable<T>> findChildren
			) 
			where T : class
		{
			yield return root;

			foreach (var child in 
				findChildren(root)
					.SelectMany(node => Recurse(node, findChildren))
					.TakeWhile(child => child != null))
			{
				yield return child;
			}

			// SAME CODE AS ABOVE IN A MORE VERBOSE FASHION
			//foreach (var node in findChildren(root))
			//{
			//    foreach (var child in Recurse(node, findChildren))
			//    {
			//        if (child == null)
			//            yield break;
					
			//        yield return child;
			//    }
			//}
		}
	}
}

Unit Test Class

单元测试班

using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Windows.Forms;
using System.Xml;
using Linq.Extensions.UnitTests.Properties;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Linq.Extensions.UnitTests
{
	[TestClass]
	public class RecurseUnitTest
	{
		[TestMethod]
		public void TestFileSystemRecursion()
		{
			const string rootPath = @"C:\inetpub\";

			foreach (var path in rootPath.Recurse(EnumerateFileSystemEntries))
			{
				WritePath(path);
			}
		}

		[TestMethod]
		public void TestFileSystemRecursionImprovedPerformance()
		{
			FileSystemInfo info = new DirectoryInfo(@"C:\inetpub\");

			foreach (var path in info.Recurse(EnumerateFileSystemEntriesImprovedPerformance))
			{
				WritePath(path);
			}
		}

		[TestMethod]
		public void TestXmlRecursion()
		{
			var xmlDocument = new XmlDocument();
			xmlDocument.LoadXml(Resources.SampleXmlData);

			foreach (var node in xmlDocument.FirstChild.Recurse(EnumerateXmlElements))
			{
				WriteXmlNode(node);
			}
		}

		[TestMethod]
		public void TestXmlRecursionReverse()
		{
			var xmlDocument = new XmlDocument();
			xmlDocument.LoadXml(Resources.SampleXmlData);

			foreach (var node in xmlDocument.FirstChild.Recurse(EnumerateXmlElements).Reverse())
			{
				WriteXmlNode(node);
			}
		}

		[TestMethod]
		public void TestWindowsFormsTreeRecursion()
		{
			var form = new FormTreeView();

			foreach (var node in form.treeView.Nodes[0].Recurse(EnumerateTreeViewNodes))
			{
				WriteTreeViewNode(node);
			}
		}

		private static void WriteTreeViewNode(TreeNode node)
		{
			Debug.WriteLine(node.Text);
		}

		private static void WriteXmlNode(XmlNode node)
		{
			Debug.WriteLine(node.Name);
		}

		public IEnumerable<TreeNode> EnumerateTreeViewNodes(TreeNode node)
		{
			return node.Nodes.Cast<TreeNode>();
		}

		public IEnumerable<XmlNode> EnumerateXmlElements(XmlNode node)
		{
			return node.ChildNodes.Cast<XmlNode>();
		}

		public IEnumerable<string> EnumerateFileSystemEntries(string root)
		{
			if (Directory.Exists(root))
			{
				foreach (var fileSystemEntry in Directory.EnumerateFileSystemEntries(root))
				{
					yield return fileSystemEntry;
				}
			}
		}

		public IEnumerable<FileSystemInfo> EnumerateFileSystemEntriesImprovedPerformance(FileSystemInfo info)
		{
			var rootDirectoryInfo = info as DirectoryInfo;

			if (rootDirectoryInfo == null) yield break;

			foreach (var directoryInfo in rootDirectoryInfo.GetDirectories())
			{
				yield return directoryInfo;
			}

			foreach (var fileInfo in rootDirectoryInfo.GetFiles())
			{
				yield return fileInfo;
			}
		}

		public static void WritePath(string path)
		{
			Debug.WriteLine(path);
		}
		
		public static void WritePath(FileSystemInfo info)
		{
			Debug.WriteLine(info.FullName);
		}
		
	}
}

Sample XML

样本XML

Just attach as a file resource inside your Unit Test project named "SampleXmlData"
<Root>
	<ChildA>
		<SubChildA></SubChildA>
		<SubChildB></SubChildB>
		<SubChildC></SubChildC>
	</ChildA>
	<ChildB>
		<SubChildA></SubChildA>
		<SubChildB></SubChildB>
	</ChildB>
	<ChildC>
		<SubChildA></SubChildA>
	</ChildC>
	<ChildD>
	</ChildD>
</Root>

Sample Form with a populated TreeView control

具有填充的TreeView控件的示例表单

namespace Linq.Extensions.UnitTests
{
	partial class FormTreeView
	{
		/// <summary>
		/// Required designer variable.
		/// </summary>
		private System.ComponentModel.IContainer components = null;

		/// <summary>
		/// Clean up any resources being used.
		/// </summary>
		/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
		protected override void Dispose(bool disposing)
		{
			if (disposing && (components != null))
			{
				components.Dispose();
			}
			base.Dispose(disposing);
		}

		#region Windows Form Designer generated code

		/// <summary>
		/// Required method for Designer support - do not modify
		/// the contents of this method with the code editor.
		/// </summary>
		private void InitializeComponent()
		{
			System.Windows.Forms.TreeNode treeNode1 = new System.Windows.Forms.TreeNode("SubChildA");
			System.Windows.Forms.TreeNode treeNode2 = new System.Windows.Forms.TreeNode("SubChildB");
			System.Windows.Forms.TreeNode treeNode3 = new System.Windows.Forms.TreeNode("SubChildC");
			System.Windows.Forms.TreeNode treeNode4 = new System.Windows.Forms.TreeNode("ChildA", new System.Windows.Forms.TreeNode[] {
            treeNode1,
            treeNode2,
            treeNode3});
			System.Windows.Forms.TreeNode treeNode5 = new System.Windows.Forms.TreeNode("SubChildA");
			System.Windows.Forms.TreeNode treeNode6 = new System.Windows.Forms.TreeNode("SubChildB");
			System.Windows.Forms.TreeNode treeNode7 = new System.Windows.Forms.TreeNode("ChildB", new System.Windows.Forms.TreeNode[] {
            treeNode5,
            treeNode6});
			System.Windows.Forms.TreeNode treeNode8 = new System.Windows.Forms.TreeNode("SubChildA");
			System.Windows.Forms.TreeNode treeNode9 = new System.Windows.Forms.TreeNode("ChildC", new System.Windows.Forms.TreeNode[] {
            treeNode8});
			System.Windows.Forms.TreeNode treeNode10 = new System.Windows.Forms.TreeNode("ChildD");
			System.Windows.Forms.TreeNode treeNode11 = new System.Windows.Forms.TreeNode("RootA", new System.Windows.Forms.TreeNode[] {
            treeNode4,
            treeNode7,
            treeNode9,
            treeNode10});
			System.Windows.Forms.TreeNode treeNode12 = new System.Windows.Forms.TreeNode("RootB");
			this.treeView = new System.Windows.Forms.TreeView();
			this.SuspendLayout();
			// 
			// treeView
			// 
			this.treeView.Dock = System.Windows.Forms.DockStyle.Fill;
			this.treeView.Location = new System.Drawing.Point(0, 0);
			this.treeView.Name = "treeView";
			treeNode1.Name = "Node7";
			treeNode1.Text = "SubChildA";
			treeNode2.Name = "Node8";
			treeNode2.Text = "SubChildB";
			treeNode3.Name = "Node9";
			treeNode3.Text = "SubChildC";
			treeNode4.Name = "Node3";
			treeNode4.Text = "ChildA";
			treeNode5.Name = "Node10";
			treeNode5.Text = "SubChildA";
			treeNode6.Name = "Node11";
			treeNode6.Text = "SubChildB";
			treeNode7.Name = "Node4";
			treeNode7.Text = "ChildB";
			treeNode8.Name = "Node12";
			treeNode8.Text = "SubChildA";
			treeNode9.Name = "Node5";
			treeNode9.Text = "ChildC";
			treeNode10.Name = "Node6";
			treeNode10.Text = "ChildD";
			treeNode11.Name = "Node0";
			treeNode11.Text = "RootA";
			treeNode12.Name = "Node2";
			treeNode12.Text = "RootB";
			this.treeView.Nodes.AddRange(new System.Windows.Forms.TreeNode[] {
            treeNode11,
            treeNode12});
			this.treeView.Size = new System.Drawing.Size(284, 262);
			this.treeView.TabIndex = 0;
			// 
			// FormTreeView
			// 
			this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
			this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
			this.ClientSize = new System.Drawing.Size(284, 262);
			this.Controls.Add(this.treeView);
			this.Name = "FormTreeView";
			this.Text = "FormTreeView";
			this.ResumeLayout(false);

		}

		#endregion

		public System.Windows.Forms.TreeView treeView;

	}
}
LinqRecurseExtension.zip LinqRecurseExtension.zip LinqRecurseExtension2008.zip LinqRecurseExtension2008.zip

翻译自: https://www.experts-exchange.com/articles/3007/A-Custom-Recursive-LINQ-Extension.html

linq 递归

 类似资料: