Перечислитель для поведения SortedDictionary.ValueCollection отличается от других Перечислителей - PullRequest
3 голосов
/ 31 января 2020

У нас недавно была ошибка из-за поведения Enumerator SortedDictionary.ValueCollection, которое ведет себя иначе, чем другие перечислители. Мне удалось сузить проблему до следующего (бессмысленного) примера:

public void Example()
{
    var sorted = new SortedDictionary<string, int>
    {
        {"1", 1 },
        {"3", 3 },
        {"0", 0 },
        {"2", 2 },
        {"4", 4 }
    };

    var fromValues = sorted.Values.GetEnumerator();
    fromValues.MoveNext();

    var fromLinq = sorted.Select(x => x.Value).GetEnumerator();
    fromLinq.MoveNext();

    var fromDictionary = new Dictionary<string, int>(sorted).Values.GetEnumerator();
    fromDictionary.MoveNext();

    for (var i = 0; i < 3; i++)
    {
        Console.WriteLine($"Printing for {i}");
        Print("  From Values:     ", fromValues, i);
        Console.WriteLine("  ------------");
        Print("  From Linq:       ", fromLinq, i);
        Console.WriteLine("  ------------");
        Print("  From Dictionary: ", fromDictionary, i);
        Console.WriteLine();
    }
}

private void Print(string prefix, IEnumerator<int> enumerator, int value)
{
    do
    {
        Console.WriteLine(prefix + "Value in loop:  " + enumerator.Current);
        if (enumerator.Current == value)
        {
            Console.WriteLine(prefix + "Selected Value: " + enumerator.Current);
            break;
        }
    } while (enumerator.MoveNext());
}

Это даст следующий вывод:

Printing for 0
  From Values:     Value in loop:  0
  From Values:     Selected Value: 0
  ------------
  From Linq:       Value in loop:  0
  From Linq:       Selected Value: 0
  ------------
  From Dictionary: Value in loop:  0
  From Dictionary: Selected Value: 0

Printing for 1
  From Values:     Value in loop:  0
  From Values:     Value in loop:  1
  From Values:     Selected Value: 1
  ------------
  From Linq:       Value in loop:  0
  From Linq:       Value in loop:  1
  From Linq:       Selected Value: 1
  ------------
  From Dictionary: Value in loop:  0
  From Dictionary: Value in loop:  1
  From Dictionary: Selected Value: 1

Printing for 2
  From Values:     Value in loop:  0
  From Values:     Value in loop:  2
  From Values:     Selected Value: 2
  ------------
  From Linq:       Value in loop:  1
  From Linq:       Value in loop:  2
  From Linq:       Selected Value: 2
  ------------
  From Dictionary: Value in loop:  0
  From Dictionary: Value in loop:  1
  From Dictionary: Value in loop:  2
  From Dictionary: Selected Value: 2

Как видите, три Iterators ведут себя по-разному:

  • SortedDictionary: Current всегда равно 0 при передаче в функцию, но MoveNext возвращает к правильному значению в l oop.
  • Linq: ведет себя так, как я ожидал Iterator.
  • Словарь: сбрасывается каждый раз, когда Enumerator передается в функцию.

Я подозреваю, что SortedDictionary.ValueCollection.Enumerator является структурой, а тот, который выдает linq, является ссылочным типом, как-то связан с ним. Но это не объясняет, почему он не ведет себя как Enumerator из словаря.

1 Ответ

1 голос
/ 31 января 2020

Я полагаю, что ответом на это является то, что перечислитель значения SortedDictionary является структурой (типа System.Collections.Generic.SortedDictionary<string,int>.ValueCollection.Enumerator).

То, что происходит, заключается в том, что структура копируется при каждой передаче в метод Print(), таким образом, метод всегда работает с копией исходного перечислителя - и это вызывает странную работу.

Следующая программа демонстрирует это:

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        Example();
    }

    public static void Example()
    {
        var sorted = new SortedDictionary<string, int>
        {
            {"1", 1 },
            {"3", 3 },
            {"0", 0 },
            {"2", 2 },
            {"4", 4 }
        };

        var fromValues1 = sorted.Values.GetEnumerator();
        // fromValue1 type is struct System.Collections.Generic.SortedDictionary<string,int>.ValueCollection.Enumerator
        fromValues1.MoveNext();

        while (printNextValue(fromValues1)) // Prints 0 once for each value in the dictionary.
            ;

        Console.WriteLine("-----------------");

        IEnumerator<int> fromValues2 = sorted.Values.GetEnumerator();
        // fromValues2 type is boxed struct System.Collections.Generic.SortedDictionary<string,int>.ValueCollection.Enumerator
        fromValues2.MoveNext();

        while (printNextValue(fromValues2)) // Prints each value in the dictionary.
            ;
    }

    static bool printNextValue(IEnumerator<int> enumerator)
    {
        Console.WriteLine(enumerator.Current);
        return enumerator.MoveNext();
    }
}

первый l oop выводит все нули, а второй выводит правильные значения.

Единственное различие между двумя циклами в этом примере состоит в том, что первый итератор объявлен как:

var fromValues1 = sorted.Values.GetEnumerator();

А второй объявлен как:

IEnumerator<int> fromValues2 = sorted.Values.GetEnumerator();.

В результате первого объявления fromValues1 будет структурой, а второе - в штучной упаковке struct.

Поскольку структура упакована, это означает, что она НЕ копируется при передаче в printNextValue(), что означает, что printNextValue() будет работать с исходным перечислителем, а не с его копией.

* 102 9 * Однако это НЕ объясняет, почему l oop завершается! Если исходная позиция перечислителя копировалась при каждом вызове printNextValue(), то l oop никогда не завершится, поскольку позиция исходного перечислителя никогда не будет обновлена.

Это заставляет меня поверить, что некоторые Сложность в реализации SortedDictionary.Enumerator означает, что при копировании структуры некоторые ее данные копируются, а некоторые нет.

(Глядя на исходный код, я подозреваю, что это связано с реализация перечислителя Current является типом значения, которое копируется, но MoveNext(), по-видимому, манипулирует стеком, который - будучи ссылочным типом - разделяется между всеми копиями перечислителя. Однако код слишком сложен для меня, чтобы проанализировать в мое в настоящее время ограниченное время ...)

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