在实现IEnumerable <t>时,会观察到指针误导</t>

时间:2012-02-16 17:58:49

标签: c#-4.0

这是我在类上实现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中。

当我运行代码片段时,我得到以下输出:

Snippet Result

因为我在新实例化的Items中将项目添加到Node,所以我不希望将该项目添加到MyItems,但这显然是正在发生的事情。

似乎Items中的Node指向MyItems中的NodeCollection

任何人都可以告诉我:

  • 为什么会这样?
  • 如何让它不会发生?

3 个答案:

答案 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();