Проблема с инициализацией списка с емкостью с использованием Enumerable.Repeat

Если мы пытаемся инициализировать список с емкостью и значением по умолчанию, точно так же, как мы инициализируем вектор в C++ с размером и значением по умолчанию, скажем, я создаю список словаря для построения графика с весом, обычно есть два способа сделать это:

1) Использование цикла for для создания списка вручную:

var graph= new List<Dictionary<int, int>>();
for (var i = 0; i < n; i++)
{
    graph.Add(new Dictionary<int, int>());
}

2) Использование Enumberable.Repeat() для инициализации списка:

var graph= Enumerable.Repeat(new Dictionary<int, int>(), n).ToList();

Скажем, теперь у нас есть список пар узлов (ребер) с весом и мы хотим построить график поверх этого, мы напишем:

foreach (var list in lists) // List = [[0, 1, 100],[1,2,100],[0,2,500]]
{
    int a = list[0], b = list[1], weight= list[2];
    if (!graph[a].ContainsKey(b))
        graph[a].Add(b, weight);
}

Хотя оба подхода теоретически должны работать, результаты совершенно разные. Когда я пытаюсь распечатать график со следующим кодом:

for (var i = 0; i < graph.Count; i++)
{
    var node = graph[i];
    foreach (var pair in node)
    {
        Console.WriteLine($"{i} -> {pair.Key} with price {pair.Value}");
    }
}

Результат 1) выглядит так:

// Correct output
0 -> 1 with price 100
0 -> 2 with price 500
1 -> 2 with price 100

Но результат 2) выглядит так:

// Incorrect result
0 -> 1 with price 100
0 -> 2 with price 100
1 -> 1 with price 100
1 -> 2 with price 100
2 -> 1 with price 100
2 -> 2 with price 100

Поведение метода Repeat кажется очень странным в этом случае и на Doc, о таком поведении не упоминается.

Я полагаю, что это поведение заключается в том, что метод Repeat создает значение ONLY ONE в памяти и указывает все записи списка на одно и то же место в памяти. В этом случае в памяти создается только один словарь вместо нового словаря, создаваемого для каждого элемента списка. И все операции со словарем на самом деле происходят в одном и том же месте. Но это не объясняет странный вывод во втором результате.

Любая мысль? Это ошибка в этом методе Enumerable.Repeat() или я делаю это неправильно? Каков наилучший способ инициализировать список значением по умолчанию?


person Aven    schedule 26.01.2020    source источник
comment
Я не думаю, что это так.   -  person Aven    schedule 26.01.2020
comment
Dictionary<int, int> — это ссылочный тип, вы повторяете значение, хранящееся в том же месте в памяти. источники показывают, что RepeatIterator возвращает то же значение в цикле, без копирования   -  person Pavel Anikhouski    schedule 26.01.2020
comment
Также актуально: копируется ли объект значения c Sharp и когда копируется его ссылка"> stackoverflow.com/questions/4347902/   -  person Progman    schedule 26.01.2020
comment
@PavelAnikhouski Но как это объясняет вывод?   -  person Aven    schedule 26.01.2020
comment
@Aven, вы изменяете один и тот же экземпляр   -  person Pavel Anikhouski    schedule 26.01.2020
comment
Так что этот метод практически бесполезен, когда вы пытаетесь инициализировать не примитивное значение?   -  person Aven    schedule 26.01.2020
comment
var graph = Enumerable.Range(0,n).Select(i => new Dictionary<int, int>()).ToList(); Я бы предпочел петлю.   -  person Jimi    schedule 26.01.2020
comment
@Aven этот метод не создает новую копию значения, он просто повторяет значение. В случае ссылочных типов каждое значение указывает на одно и то же место в памяти. Следовательно, вы получите неверный вывод, потому что модифицируете тот же экземпляр и не можете добавить [0,2,500] в словарь вторым способом (ключ со значением 2 в этом случае уже существует)   -  person Pavel Anikhouski    schedule 26.01.2020


Ответы (1)


Enumerable.Repeat просто дублирует переданное значение n раза. Вы создаете один словарь, передаете его Repeat, и он создает список n ссылок на этот словарь.

Если вы хотите создать последовательность на основе функции, воспользуйтесь MoreLinq Generate расширение.

person V0ldek    schedule 26.01.2020
comment
Или что-то вроде Enumerable.Range(0, n).Select(_ => new Dictionary<...>()) - person Joey; 26.01.2020