我正在编写将从树结构返回特定节点的函数。但是当我使用LINQ在树中搜索时,它会在第一个分支中搜索,最后当它到达叶子时,它会抛出空引用异常,因为叶子没有任何孩子。
这是我的班级,
public class Node
{
public int Id { get; set; }
public string Name { get; set; }
public string Content { get; set; }
public IEnumerable<Node> Children { get; set; }
public IEnumerable<Node> GetNodeAndDescendants() // Note that this method is lazy
{
return new[] { this }
.Concat(Children.SelectMany(child => child.GetNodeAndDescendants()));
}
}
这就是我调用此函数的方式,
var foundNode = Location.GetNodeAndDescendants().FirstOrDefault(node => node.Name.Contains("string to search"));
OR
var foundNode = Location.GetNodeAndDescendants().FirstOrDefault(node => node.Id==123)
这样做的正确方法是什么?任何示例代码都会感激不尽
答案 0 :(得分:0)
如果你不介意依赖第三方解决方案,我有一个我一直在研究的轻量级库,它可以用任何树来完成这个和许多其他事情。它被称为Treenumerable
。你可以在GitHub上找到它:https://github.com/jasonmcboyd/Treenumerable;和NuGet上的最新版本(此时为1.2.0):http://www.nuget.org/packages/Treenumerable。它具有良好的测试覆盖率,似乎很稳定。
它确实需要您创建一个帮助器类,该类使用两种方法实现ITreeWalker
接口:TryGetParent
和GetChildren
。正如您可能猜到的那样TryGetParent
获取节点的父节点,因此您的Node
类必须以其知道其父节点的方式进行修改。我想你可以在NotSupported
中抛出TryGetParent
异常,因为任何遍历操作都不需要该方法。无论如何,无论你走哪条路,下面的代码都会做你想要的:
ITreeWaler<Node> walker;
// Don't forget to instantiate 'walker'.
var foundNode =
walker
.PreOrderTraversal(Location)
.FirstOrdefault(node => node.Name.Contains("string to search"));
我的实现和你的实现之间值得一提的是我的实现不依赖于递归。这意味着您不必担心投掷StackOverflowException
的深树。
答案 1 :(得分:0)
编写自己的函数没有错,但基于LINQ或递归迭代器的实现不是一个好主意(性能!)。但为什么依赖外部库?很多你不需要的代码,实现接口,修改你的类等。为预订树遍历编写泛型函数并将其用于任何树结构并不困难。这是我参与How to flatten tree via LINQ?的修改版本(没有什么特别的,普通的迭代实现):
public static class TreeHelper
{
public static IEnumerable<T> PreOrderTraversal<T>(T node, Func<T, IEnumerable<T>> childrenSelector)
{
var stack = new Stack<IEnumerator<T>>();
var e = Enumerable.Repeat(node, 1).GetEnumerator();
try
{
while (true)
{
while (e.MoveNext())
{
var item = e.Current;
yield return item;
var children = childrenSelector(item);
if (children == null) continue;
stack.Push(e);
e = children.GetEnumerator();
}
if (stack.Count == 0) break;
e.Dispose();
e = stack.Pop();
}
}
finally
{
e.Dispose();
while (stack.Count != 0) stack.Pop().Dispose();
}
}
}
并且您在class Node
内的功能变为:
public IEnumerable<Node> GetNodeAndDescendants() // Note that this method is lazy
{
return TreeHelper.PreOrderTraversal(this, node => node.Children);
}
其他一切都按照您的方式进行,并且无需任何问题。
编辑:看起来你需要这样的东西:
public interface IContainer
{
// ...
}
public class CustomerNodeInstance : IContainer
{
// ...
}
public class ProductNodeInstance : IContainer
{
// ...
}
public class Node : IContainer
{
// ...
public IEnumerable<IContainer> Children { get; set; }
public IEnumerable<IContainer> GetNodeAndDescendants() // Note that this method is lazy
{
return TreeHelper.PreOrderTraversal<IContainer>(this, item => { var node = item as Node; return node != null ? node.Children : null; });
}
}