2012-01-29 1 views
3

Мы с коллегой обсуждали ситуацию, когда запрос IEnumerable возвращал старое результирующее множество после повторной инициализации исходного списка. Где-то в выполнении приложения список устанавливался в нуль и повторно заполнялся новыми значениями. Сам запрос никогда не переопределялся и продолжал возвращать старые результаты. На самом деле, даже не важно, остался ли исходный список null; старые результаты все еще возвращались.Запрос LINQ возвращает старые результаты, когда исходный список повторно инициализирован

Вот несколько модульных тестов, чтобы продемонстрировать, что мы видим:

[Test] 
public void QueryResultsBasedOnCurrentListEvenAfterUpdate() 
{ 
    var list = new List<string> { "Two", "Three" }; 
    var query = list.Where(x => x.Length > 3); 

    var result1 = query.ToList(); 

    list.Clear(); 
    list.AddRange(new List<string> { "Four", "Five", "One" }); 

    //Correctly gets an updated result set 
    var result2 = query.ToList(); 

    //PASS 
    CollectionAssert.AreEquivalent(new List<string> { "Three" }, result1); 

    //PASS 
    CollectionAssert.AreEquivalent(new List<string> { "Four", "Five" }, result2); 
} 

[Test] 
public void QueryResultsBasedOnCurrentListEvenAfterSetToNullAndReInstantiated() 
{ 
    var list = new List<string> { "Two", "Three" }; 
    var query = list.Where(x => x.Length > 3); 

    var result1 = query.ToList(); 

    list = null; 
    list = new List<string> { "Four", "Five", "One" }; 

    var result2 = query.ToList(); 

    //PASS 
    CollectionAssert.AreEquivalent(new List<string> { "Three" }, result1); 

    //FAIL : result2 == result1. The query wasn't evaluated against the new list 
    CollectionAssert.AreEquivalent(new List<string> { "Four", "Five" }, result2); 
} 

[Test] 
public void QueryExecutionThrowsExceptionWhenListIsSetToNull() 
{ 
    var list = new List<string> { "Two", "Three" }; 
    var query = list.Where(x => x.Length > 3); 

    list = null; 

    //FAIL : The query is still evaluated against the original list 
    Assert.Throws<ArgumentNullException>(() => query.ToList()); 
} 

кажется, что, несмотря на отложенное выполнение, эти запросы по-прежнему указывает на исходный список. Пока исходная коллекция, которая была построена против запроса, остается живой, запрос правильно оценивает результаты. Однако, если список повторно создается, запрос остается привязанным к исходному списку.

Что мне не хватает? Пожалуйста, объясните ...

UPDATE:
Я вижу такое же поведение для запроса, построенного как IQueryable. Имеет ли ссылка IQueryable ссылку на исходный список?

ответ

9

Я вижу, как это будет немного запутанным. Вот сделка, лаконично:

  • «приемник» запроса рассматривается как значение- это никогда не изменится. Если значение является ссылкой на список, значение продолжает ссылаться на этот список.Содержимое списка может измениться, но , список не изменяется.

  • Локальные переменные, на которые ссылаются предложения запроса, рассматриваются как переменные - самые последние значения этих переменных всегда используются.

Позвольте мне привести аналогию с реальным миром. Предположим, вы на кухне. У вас есть ящик с надписью «дом» и ящик с надписью «имя». В ящике с надписью «дом» есть лист бумаги, в котором говорится «1600 Пенсильванская авеню». В ящике с надписью «имя» есть лист бумаги, в котором говорится «Мишель». Когда вы говорите:

var house = GetHouse("1600 Pennsylvania Avenue"); 
var name = "Michelle"; 
var query = from resident in house.Residents 
      where resident.FirstName == name 
      select resident; 

Это похоже на написание запроса:

"list all the residents of the White House whose first name is (look at the piece of paper in the drawer marked "name" in my kitchen)" 

Значение, возвращаемое этот запрос зависит от (1), который живет в Белом доме, и (2), какое имя записывается на листе бумаги при выполнении запроса.

Это не как писать запрос:

"list all the residents of (look at the piece of paper in the drawer marked "house" in my kitchen) whose first name is (look at the piece of paper in the drawer marked "name" in my kitchen)" 

объект, против которого запрос выполняется не переменная. Содержание Белого дома может измениться. Имя, о котором спрашивает запрос, может измениться. И поэтому результаты запроса могут меняться двумя способами - со временем и значением переменной name. Но какой дом запрашивает вопрос о не меняется, независимо от того, что вы делаете с переменной , которая содержала ссылку. Эта переменная не имеет отношения к запросу; для построения запроса использовался его значение .

3

Изменение, на которое указывает ссылочный «список», не изменяет исходные данные. Когда выражение запроса было написано, метод «Где» взял свою собственную ссылку на данные и будет работать над этими данными независимо от того, где впоследствии указывается переменная «список».

Это этот случай, Where получает новый экземпляр IEnumerable, который ссылается на данные, которые list в настоящее время указывает, а когда вы измените то, что список указывает на, то IEnumerable не изменится, то уже это ссылка на данные.

+0

Хороший ответ. Также случай с Linq к объектам. Там уже есть материализованный список. Это не похоже на возвращение IQueryable и построение «запроса», которое еще не материализовалось. –

+0

@ latr0dectus: Я вижу то же поведение для запроса IQueryable. – curtisthibault

1

Это так просто, как query (через IEnumerable) содержит ссылку на коллекцию, с которой она была создана.

Изменение другой переменной, ссылающейся на тот же список (т. Е. Установка list на null) не меняет ссылку query.

  • Первый тест изменяет исходные данные в фактических список query ссылок, которые действительно изменяет результат.

  • Во втором тесте вы создаете новый список, который оставляет query, все еще ссылаясь на предыдущий список. Изменение list не меняется query.

  • Третий тест отменяет только list, который не влияет на ссылку query.