这是我在类上实现IEnumerable时遇到的一个有趣错误。它似乎类似于“访问修改后的闭包”问题,但我对如何解决它感到茫然。
以下是一个演示此问题的简单示例:
void Main()
{
var nodeCollection = new NodeCollection();
nodeCollection.MyItems = new List<string>() { "a", "b", "c" };
foreach (var node in nodeCollection)
{
node.Dump();
}
}
public class NodeCollection : IEnumerable<Node>
{
public List<string> MyItems;
public IEnumerator<Node> GetEnumerator()
{
// This isn't necessary, but it should prove that it's not an "access to modified closure" issue.
var items = MyItems;
for (var i = 0; i < 3; i++)
{
var node = new Node();
// I want the node to contains the items in MyItems.
node.Items = items;
// Plus an additional item. Note that I am adding the item to the node, NOT to MyItems.
node.Items.Add(string.Format("iteration: {0}", i));
yield return node;
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
public class Node
{
public List<string> Items;
}
从Dump()
语句中可以看出,我在LINQPad中运行它,但问题将出现在任何IDE中。
当我运行代码片段时,我得到以下输出:
因为我在新实例化的Items
中将项目添加到Node
,所以我不希望将该项目添加到MyItems
,但这显然是正在发生的事情。
似乎Items
中的Node
指向MyItems
中的NodeCollection
。
任何人都可以告诉我:
答案 0 :(得分:2)
您正在每次迭代创建新节点,但随后将相同的items
实例设置为每个节点的Items
属性。然后,您将迭代字符串添加到Items集合中存储的items实例(它始终是相同的实例),从而导致每个后续节点具有越来越多的“迭代”条目。如果你保留了所有节点,你会发现它们都具有完全相同的Items
值。
我认为这里的基本误解是你假设设置Node(node.Items = items;
)的Items属性会将items
列表复制到节点中。事实上,它所做的就是将node.Items设置为指向您调用items
的已存在列表。
这可以让你知道出错的地方:
// This same instance of items is being reused each time
var items = MyItems;
for (var i = 0; i < 3; i++)
{
var node = new Node();
// I want the node to contains the items in MyItems.
// Assuming node.Items is a List<String>
node.Items = new List<String>();
node.Items.AddRange(items);
node.Items.Add(string.Format("iteration: {0}", i));
yield return node;
}
答案 1 :(得分:2)
node.Items = items;
将node.Items
设置为对items
列表的引用。只有一个列表,有几个引用它。
我想你想要的是在每个节点中有一个单独的列表,并且你想要将items
中的元素复制到该列表中。为此,请创建一个包含items
中所有元素的新列表。
node.Items = new List<string>(items);
答案 2 :(得分:0)
当你这样做时: var item = MyItems; 您只需创建对MyItems的引用并将其存储在变量项中。然后你做的时候: node.Items = items; 您只需使用相同的引用并将其存储在node.Items中。如果您需要node.Items作为新列表(指向不同的内存位置),请再次初始化它。 node.Items = new List();