C# 如何最好地遍历子对象的子对象&x27;把孩子带到未知的深度?
想象一个具有以下特性的对象:C# 如何最好地遍历子对象的子对象&x27;把孩子带到未知的深度?,c#,linq,C#,Linq,想象一个具有以下特性的对象: class TestObject { public string Name { get; set; } public Collection<TestObject> Children { get; set; } } 以未知深度检索包含每个子级的结果的最佳方法是什么?Helper方法 评论 您可以轻松地修改遍历方法,使其完全按照您想要的方式运行。例如,如果您只需
class TestObject
{
public string Name { get; set; }
public Collection<TestObject> Children { get; set; }
}
以未知深度检索包含每个子级的结果的最佳方法是什么?Helper方法
评论
您可以轻松地修改遍历
方法,使其完全按照您想要的方式运行。例如,如果您只需要叶节点,那么您应该只产生返回根当子项
为null或空时,code>
性能问题
如果性能是任何问题,请考虑上面的LINQ/函数实现,或者看看Servy的答案,其中的任何一个都应该比使用<代码>收益>…/代码>的版本更有效。
< p>您可以编写这样的泛型遍历方法:
public static IEnumerable<T> Traverse<T>(T root,
Func<T, IEnumerable<T>> childSelector)
{
var stack = new Stack<T>();
stack.Push(root);
while (stack.Any())
{
var next = stack.Pop();
yield return next;
foreach (var child in childSelector(next))
stack.Push(child);
}
}
公共静态IEnumerable遍历(T根,
Func(儿童选择器)
{
var stack=新堆栈();
栈.推(根);
while(stack.Any())
{
var next=stack.Pop();
其次是收益率;
foreach(childSelector中的var child(下一个))
栈.推(子);
}
}
这是一个通用模型,通常用于遍历树。请注意,这将执行深度优先搜索。如果您想进行呼吸优先搜索,您可以使用队列
而不是堆栈
如何初始化数据?上面的代码不会创建一个超过一级的树。应该避免递归迭代器块。与传统方法不同,它们相当“重”,并且与状态和构造/破坏关联更多。相反,编写一个非递归遍历函数将有相当明显的性能改进。@Servy Point做得很好。然而,OP可能对这里的性能一点也不感兴趣。我个人会从这个实现开始,只有当我发现它太慢的时候才会改变它;它被设计成通用的东西,你可以在很多地方使用。这是更有可能值得优化的东西。优化实际上使方法更简单、更容易使用、更灵活,而且性能更高,使决策更容易。@Servy优化并没有使方法更简单。您的方法看起来更简单的原因是,我正在执行的null
检查没有出现在您的方法中。如果删除null
检查,我的代码可以写4行。看一看编辑后的答案。@Servy另外,我上面的第三个实现(使用函数LINQ)应该更有效。使用堆栈非常有趣-处理一些性能数字来比较两个答案。谢谢。在我的性能测试中,与LINQ/递归方法相比,我得到了10-13%的性能提升。虽然性能不是最初问题的一部分,但值得注意。
// Works
var joe = person1.Children.SelectMany(c => c.Children).Concat(person1.Children);
// Does not work - only returns 1 level deep
var mary = person2.Children.SelectMany(c => c.Children).Concat(person2.Children);
public static IEnumerable<T> Traversal<T>(
T root,
Func<T, IEnumerable<T>> getChildren)
{
if (root == null)
{
yield break;
}
yield return root;
var children = getChildren(root);
if (children == null)
{
yield break;
}
foreach (var child in children)
{
foreach (var node in Traversal(child, getChildren))
{
yield return node;
}
}
}
//Or if you don't need all those null checks, here's a more compact version.
public static IEnumerable<T> Traversal<T>(
T root,
Func<T, IEnumerable<T>> getChildren)
{
yield return root;
foreach (var child in getChildren(root))
foreach (var node in Traversal(child, getChildren))
yield return node;
}
//If you like a LINQ/functional style better, this is also equivalent.
public static IEnumerable<T> Traversal<T>(
T root,
Func<T, IEnumerable<T>> getChildren)
{
return new T[] { root }
.Concat(getChildren(root)
.SelectMany(child => Traversal(child, getChildren)));
}
var everybody = Traversal(person, x => x.Children);
public static IEnumerable<T> Traverse<T>(T root,
Func<T, IEnumerable<T>> childSelector)
{
var stack = new Stack<T>();
stack.Push(root);
while (stack.Any())
{
var next = stack.Pop();
yield return next;
foreach (var child in childSelector(next))
stack.Push(child);
}
}