我想建一个这样的树结构:
root Id 1
child id 2
grandChild id 3下面的代码示例。如果我使用GetChildrenNodesCorrect(),,就会得到正确的结果。但是,当使用GetChildrenNodesWrong()时,它返回如下:
root Id 1
child id 2
Null我知道ToList()不是延迟执行,而是立即返回结果。有人能解释一下吗?
public class ToListTest
{
public static void Entry()
{
var toListTest = new ToListTest();
toListTest.Test();
}
public void Test()
{
List<Node> newsList = new List<Node>
{
new Node{Id = 1, ParentId = 0},
new Node{Id = 2, ParentId = 1},
new Node{Id = 3, ParentId = 2}
};
var root = BuildUpTree(newsList);
}
private TreeNode BuildUpTree(List<Node> newsList)
{
var root = new TreeNode { currentNode = newsList.First(n => n.ParentId == 0) };
BuildUpTreeChildrenNodes(newsList, root);
return root;
}
private void BuildUpTreeChildrenNodes(List<Node> newsList, TreeNode currentTreeNode)
{
currentTreeNode.Children = GetChildrenNodesWrong(newsList, currentTreeNode);
foreach (var node in currentTreeNode.Children)
{
BuildUpTreeChildrenNodes(newsList, node);
}
}
private IEnumerable<TreeNode> GetChildrenNodesWrong(List<Node> newsList, TreeNode cuurentNode)
{
return newsList.Where(n => n.ParentId == cuurentNode.currentNode.Id)
.Select(n => new TreeNode
{
currentNode = n
});
}
private IEnumerable<TreeNode> GetChildrenNodesCorrect(List<Node> newsList, TreeNode cuurentNode)
{
return GetChildrenNodesWrong(newsList, cuurentNode).ToList();
}
public class TreeNode
{
public Node currentNode { get; set; }
public IEnumerable<TreeNode> Children { get; set; }
}
public class Node
{
public int Id { get; set; }
public int ParentId { get; set; }
}
}更新
在调试中,当使用GetChildrenNodesWrong(),根时,在方法返回之前同时具有子和外孙。方法返回后,根只有一个子,而孙子是空的。
更新2
海事组织,问题可能与清洁守则无关。但欢迎任何人展示更直观的代码。
发布于 2013-05-10 20:23:20
每次评估IEnumerable时,都会重新执行Linq查询。因此,在计算树时,它是为节点分配空间,而不是将它们分配给任何永久变量。这意味着在foreach循环中,在BuildUpTreeChildrenNodes中,您不会调用所需节点实例上的递归函数。相反,您是在由foreach循环(枚举IEnumerable)创建的节点的重新实例化版本上调用它。相反,当您在ToList上调用IEnumerable时,foreach循环将返回列表中的元素,该元素位于内存中。
如果您使root公开静态,然后调试您的代码,您将看到当您调用BuildUpTreeChildrenNodes时,node参数不是您想要的节点的实例。即使它具有相同的ID并表示图中的相同节点,但它实际上并不是以任何方式连接到根节点。检查:
root.Children.Any(n => n.Id == node.Id) //true
root.Children.Contains(node) //false看到你的问题最简单的方法是在这里:
//Create a singleton Node list:
var nodeSingleton= Enumerable.Range(0, 1).Select(x => new Node { Id = x });
Console.Write(nodeSingleton.Single() == nodeSingleton.Single());您可能期望它返回true,但实际上它将是false --在调用Single Linq方法时,重新计算单个变量的延迟执行,并返回Node类的不同实例。
但是,如果在单例上调用ToList,则在内存中获得列表,Single方法将返回Node的相同实例。
更广泛地说,我认为这段代码的问题在于它把命令式代码和函数代码混为一谈。奇怪的是,这么多的方法都是void,而GetChildrenNodesWrong方法却不是。我认为你应该选择一种风格,并坚持它,因为转换范式可能会令人困惑。
发布于 2013-05-10 17:54:03
我不太清楚你在问什么。所有LINQ查询都有延迟执行,当您调用ToList()时,您只是强制执行该查询。我认为主要的问题在于你的where条款。只有两个对象满足条件,因此LINQ查询返回的IEnumerable应该只有2个对象。
由于GetChildrenNodesWrong中的LINQ查询产生了"off by one“错误,所以它没有执行您的期望。这里基本上是发生了什么;
1)我们把它喂根,n=根,什么也不发生。我们移到下一个节点。
2) n.Id = 1,当节点2的parentId为1时,节点2满足该条件。
3)我们到了第三个节点。n.ParentId = 2和current.Id =2。我们有一个匹配,所以我们给节点3分配了另一个节点和点电流。
4)我们在名单的末尾。孙子从来没有分配过,因为我们只有一个孩子。
基本上,您迭代x时间,其中x是列表的长度,但是由于在第一次迭代中,current =n没有分配一个节点,所以当您期望x时,就会得到x-1节点。
https://stackoverflow.com/questions/16488011
复制相似问题