При реализации IEnumerable <T>наблюдается неправильное направление указателя - PullRequest
2 голосов
/ 16 февраля 2012

Это интересная ошибка, с которой я столкнулся при реализации 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 ]

2 голосов
/ 16 февраля 2012

node.Items = items; устанавливает node.Items как ссылку на список items.Существует только один список с несколькими ссылками на него.

Я предполагаю, что вам нужно иметь отдельный список в каждом узле, и вы хотите скопировать элементы из items в этот список.Для этого создайте новый список, содержащий все элементы из items.

node.Items = new List<string>(items);
2 голосов
/ 16 февраля 2012

Вы создаете новые узлы на каждой итерации, но затем присваиваете тот же экземпляр items для свойства Items каждого узла. Затем вы добавляете строку итерации к экземпляру элементов, хранящемуся в коллекции элементов (который всегда является одним и тем же экземпляром), в результате чего каждый последующий узел имеет все больше и больше записей «итерации». Если вы сохраните все узлы, вы обнаружите, что все они имеют одинаковое значение Items.

Я думаю, что основное недоразумение здесь заключалось в том, что вы предполагали, что установка свойства Items узла (node.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;
    }
0 голосов
/ 16 февраля 2012

Когда вы делаете: var item = MyItems;Вы просто создаете ссылку на MyItems и сохраняете ее в элементе переменной.Затем, когда вы делаете: node.Items = items;Вы просто берете ту же ссылку и сохраняете ее в node.Items.Если вам нужен узел. Элементы должны быть новым списком (указать на другое место в памяти), инициализировать его снова.node.Items = new List ();

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...