2010-11-03 2 views
5

IEnumerable - это запрос, который оценивается лениво. Но, видимо, мое понимание немного испорчено. Я ожидаю, что следующие работы:Изменения в IEnumerable не поддерживаются между запросами

 // e.Result is JSON from a server 
     JObject data = JObject.Parse(e.Result); 
     JsonSerializer serializer = new JsonSerializer(); 

     // LINQ query to transform the JSON into Story objects 
     var stories = data["nodes"].Select(
        obj => obj["node"]).Select(
         storyData => storyOfJson(serializer, storyData)); 

     // set a value on each story returned by the query 
     foreach (Story story in stories) 
     { 
      story.Vid = vid; 
     } 

     // run through the query again, making sure the value was actually set 
     foreach (Story story in stories) 
     { 
      // FAILS - story.VID is 0 
      Debug.Assert(story.Vid == vid); 
     } 

Что я не понимаю здесь? Как я могу изменить результаты возврата этого запроса?

+0

Привет, в чем ошибка? Где инициализируется vid? – LesterDove

+1

«IEnumerable » - это просто последовательность элементов (таких как массив). Методы LINQ и другие методы итератора возвращают реализацию IEnumerable , которая оценивается лениво. – SLaks

ответ

10

Каждый раз, когда вы перечисляете переменную stories, вызов Select снова запускается, создавая новый набор объектов Story.

Таким образом, каждый цикл foreach работает на другом наборе из Story экземпляров.

Вам необходимо принудительно выполнить вызовы LINQ один раз, позвонив по номеру .ToArray().
Цитирование через результирующий массив не будет переоценивать вызовы LINQ (поскольку это обычный массив), поэтому вы будете использовать один и тот же набор из Story экземпляров.

+3

Обратите внимание, что использование любого типа коллекции будет прекрасным (i.е. К списку()). +1. –

+0

Выбор не запускается до тех пор, пока не будут перечислены истории в первый раз, но выбор не будет выполняться снова во втором перечислении. –

+4

@ Dennis: Неправильно. Он будет запускаться снова каждый раз, когда вы используете новый счетчик. (Результаты не кэшируются нигде) – SLaks

1

В этом случае IEnumerable содержит возвращаемые значения storyOfJson, вызываемые повторно по коллекции, а не оригинальные значения.

Когда вы перечислить коллекцию внутри foreach, функция вызывается повторно по мере необходимости, и результат вызова функции помещается в переменную foreach итерации (story в данном случае).

Если вы хотите сохранить набор объектов Story, вам необходимо сначала поместить их в какую-то форму, потому что перечисленные элементы будут уничтожены после завершения цикла.

Боковое примечание. Возможно, вы должны использовать синтаксис LINQ, а не функциональный синтаксис, когда это возможно.

var stories = data["nodes"].Select(obj => obj["node"]).Select(storyData => storyOfJson(serializer, storyData)); 

становится

var stories = from node in data["nodes"] 
       select storyOfJson(serializer, node["node"]); 
+0

Спасибо за синтаксис LINQ. Я не был уверен, как заставить его работать, но это намного лучше. –

1

IEnumerable должен рассматриваться как набор только для чтения. Хотя некоторые программы могут возвращать существующую коллекцию, а не копию, другие программы могут возвращать новую копию каждый раз. Например, когда вы добавляете .Select(storyData => storyOfJson(...)), вы будете получать новые объекты каждый раз.

Это означает, что если вы отредактируете полученную копию, затем снова выполните запрос/вызов, затем ваши изменения будут уничтожены.

Если вы хотите изменить результаты запроса, либо добавить свой собственный .Select() поверх полученного запроса, чтобы выполнить преобразование вы заинтересованы в АЛК:

var filteredStoried = stories.Select(s => new Story(s) { Vid = vid }); 

foreach(var s in filteredStories) 
    Assert(s.Vid == vid); 

Или скопировать результаты запроса в местную коллекцию и работа с этим:

var localStories = new List<Story>(stories); 

foreach(var s in localStories) 
    s.Vid = vid; 

foreach(var s in localStories) 
    Assert(s.Vid == vid); 
+0

Не добавила бы, что 'Select' сверху будет пустой тратой? –

+0

@ Rosarch: Он каждый раз создает новые объекты. Является ли это отходами или нет, зависит от общей архитектуры программы. Если кто-то редактирует коллекцию, то это не повлияет на локальный экземпляр вашего класса. Это облегчает работу с программой без ошибок. Если позже вы определите, что это узкое место производительности (через профилирование), вы всегда можете изменить его, чтобы каждый раз возвращать один и тот же экземпляр. –

+0

@ Rosarch: Btw, 'ToList()' ответ в основном такой же, как второй ответ, который я вам дал здесь. Синтаксис для 'ToList()' лучше, конечно. Оба создадут новый объект «Список », который выделяет дополнительное хранилище для хранения всех ссылок. –

Смежные вопросы