Запрос LINQ возвращает старые результаты при повторной инициализации списка источников - PullRequest
3 голосов
/ 29 января 2012

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

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

[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());
}

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

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

ОБНОВЛЕНИЕ:
Я наблюдаю то же поведение для запроса, построенного как IQueryable.IQueryable также содержит ссылку на исходный список?

Ответы [ 3 ]

9 голосов
/ 29 января 2012

Я вижу, как это немного сбивает с толку.Вот краткая сделка:

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

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

Позвольте привести аналогию с реальным миром.Предположим, вы на своей кухне.У вас есть ящик с надписью «дом» и ящик с именем «имя».В ящике с надписью "дом" есть лист бумаги с надписью "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 голосов
/ 29 января 2012

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

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

1 голос
/ 29 января 2012

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

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

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

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

  • Третий тест только обнуляет list, который не оказывает влияния на эталонное значение query уже выполняется.

...