3

我想建立一个这样的树结构:

 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(),root 时,方法返回之前既有子也有孙。方法返回后,root只有child,而grandchild为null。

更新 2

IMO,问题可能与干净的代码无关。但欢迎任何人展示更直观的代码。

4

2 回答 2

1

每次IEnumerable评估时,都会重新执行 Linq 查询。因此,当您计算树时,它会为节点分配空间,但不会将它们分配给任何永久变量。这意味着在foreach循环中BuildUpTreeChildrenNodes,您不会在所需节点的实例上调用递归函数。foreach相反,您在循环创建的节点的重新实例化版本上调用它(它枚举IEnumerable. 当您改为调用ToList时,循环将返回列表中的元素,该元素位于内存中。IEnumerableforeach

如果您将rootpublic static,然后调试您的代码,您会看到当您调用 时BuildUpTreeChildrenNodesnode参数不是您想要的节点的实例。即使它具有相同的 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-10T20:23:20.803 回答
0

我不完全确定你在问什么。所有 LINQ 查询都有延迟执行,当您调用时,ToList()您只是强制执行查询。我认为主要问题出在你的 where 子句中。只有两个对象满足条件,因此 LINQ 查询返回的 IEnumerable 应该只有 2 个对象。

它没有按照您的预期进行,因为 LINQ 查询GetChildrenNodesWrong正在产生一个“off by one”错误。这基本上是发生了什么;

1)我们为 n = root 提供 root 没有任何反应。我们移动到下一个节点。

2)n.Id = 1,节点2满足where条件,因为它的parentId为1。我们分配一个新节点,将current指向节点2

3)我们现在到达第三个节点。n.ParentId = 2 和 current.Id = 2。我们有一个匹配,所以我们分配了另一个节点并将 current 指向节点 3。

4)我们在列表的末尾。孙子永远不会被分配,因为我们少了一个。

基本上你迭代 x 时间,其中 x 是列表的长度,但是因为第一次迭代时 current = n 你没有分配节点,所以当你期望 x 时你最终得到 x -1 个节点。

于 2013-05-10T17:54:03.477 回答