Linq ToList()执行

时间:2013-05-10 17:45:11

标签: c#

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

 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,问题可能与清洁代码无关。但欢迎任何人展示更直观的代码。

2 个答案:

答案 0 :(得分:1)

每次评估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方法不是。我认为你应该选择一种风格并坚持下去,因为切换范式可能会令人困惑。

答案 1 :(得分:0)

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

由于GetChildrenNodesWrong中的LINQ查询产生“off by one”错误,因此它没有达到预期效果。这基本上是发生了什么;

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

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

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

4)我们在列表的末尾。大孩子永远不会被分配,因为我们已经离开了。

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