C# Linq ToList()执行

C# Linq ToList()执行,c#,C#,我想建立一个树状结构,如下所示: root Id 1 child id 2 grandChild id 3 下面是代码示例。如果我使用GetChildrenNodesCorrect(),我会得到正确的结果。但是当使用getChildrenNodesError()时,它返回以下结果: root Id 1 child id 2 Null 我知道ToList()不是延迟执行,而是立即返回结果。有人能解释一下吗 public class ToListTest

我想建立一个树状结构,如下所示:

 root  Id 1
   child id 2
     grandChild  id 3
下面是代码示例。如果我使用
GetChildrenNodesCorrect(),
我会得到正确的结果。但是当使用
getChildrenNodesError()
时,它返回以下结果:

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; }

       }
    }
公共类ToListTest
{
公共静态无效条目()
{
var toListTest=新的toListTest();
tolistest.Test();
}
公开无效测试()
{
列表新闻列表=新列表
{
新节点{Id=1,ParentId=0},
新节点{Id=2,ParentId=1},
新节点{Id=3,ParentId=2}
};
var root=BuildUpTree(新闻列表);
}
私有树节点构建树(列表新闻列表)
{
var root=newtreenode{currentNode=newsList.First(n=>n.ParentId==0)};
BuildUpTreeChildrenNodes(新闻列表,根节点);
返回根;
}
私有void BuildUpTreeChildrenNodes(列表新闻列表、TreeNode currentTreeNode)
{
currentTreeNode.Children=GetChildrenNodesError(新闻列表,currentTreeNode);
foreach(currentTreeNode.Children中的var节点)
{                
BuildUpTreeChildrenNodes(新闻列表,节点);
}
}
private IEnumerable GetChildrenNodesError(列表新闻列表、TreeNode cuurentNode)
{
返回newsList.Where(n=>n.ParentId==cuurentNode.currentNode.Id)
.选择(n=>新树节点
{
currentNode=n
});
}
私有IEnumerable GetChildrenNodesCorrect(列表新闻列表、TreeNode cuurentNode)
{
返回GetChildrenNodesError(新闻列表,cuurentNode).ToList();
}
公共级树节点
{
公共节点currentNode{get;set;}
公共IEnumerable子项{get;set;}
}
公共类节点
{
公共int Id{get;set;}
public int ParentId{get;set;}
}
}
更新

在调试中,当使用
getChildrenNodesError()时,
root在方法返回之前同时具有子对象和孙子对象。方法返回后,root只有子对象,孙子对象为null

更新2


在国际海事组织,这个问题可能与干净的代码无关。但是欢迎任何人展示更直观的代码。

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

它没有达到您期望的效果,因为
getChildrenNodesError
中的LINQ查询正在生成一个“off by one”错误。这里是发生的基本情况

1) 我们给它加根,n=root,什么都没发生。我们移动到下一个节点

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

3) 我们现在进入第三个节点。n、 ParentId=2且当前.Id=2。我们有一个匹配项,所以我们分配了另一个节点并将当前点指向节点3

4) 我们在名单的最后。孙子永远不会被分配,因为我们只差一个


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

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

如果将
root
设置为public static,然后调试代码,您将看到在调用
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方法的两次过程中,都会重新计算singleton变量的延迟执行,并返回
节点
类的不同实例

但是,如果在单例上调用
ToList
,则会在内存中获得列表,
Single
方法将返回与
节点相同的实例


更广泛地说,我认为这段代码的问题在于它把命令式代码和函数式代码混合得太多了。奇怪的是,这么多方法都是
void
,而
getchildrennodesError
方法却不是。我认为你应该选择一种风格并坚持下去,因为转换范式可能会让人困惑。

孙子是空的,请看我的更新。+1问得好。考虑一个类比。想象你正在建造一堵乐高积木墙。将每个乐高积木堆叠在一个合适的乐高积木上,这一点很重要,并且乐高积木是整个系统的一部分