重新初始化源列表时,LINQ查询返回旧结果

时间:2012-01-29 07:31:25

标签: c# linq ienumerable iqueryable

一位同事和我正在讨论在重新初始化源列表后IEnumerable查询返回旧结果集的情况。在执行应用程序的某个地方,列表被设置为null并重新填充新值。查询本身从未重新定义,并继续返回旧结果。事实上,如果源列表仍然存在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 个答案:

答案 0 :(得分: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)"

运行查询的对象不是变量。白宫的内容可以改变。查询询问的名称可能会更改。因此,查询的结果可以通过两种方式改变 - 使用时间和名称变量的值。但无论您对保存引用的变量做什么,查询询问的内容都不会改变。该变量与查询无关;其用于构建查询。

答案 1 :(得分:3)

更改引用“list”指向的位置不会更改原始数据。在编写查询表达式时,“Where”方法将其自己引用到数据中,并且无论“list”变量随后指向何处,都将处理该数据。

在这种情况下,Where获取IEnumerable的新实例,该实例引用list当前指向的数据,当您更改列表指向的内容时,IEnumerable不会改变,它已经有了对数据的引用。

答案 2 :(得分:1)

就像query(通过IEnumerable)拥有对其创建的集合的引用一样简单。

更改引用相同List的另一个变量(即将list设置为null)不会更改已存在的引用query

  • 第一个测试更改了实际列表query引用中的基础数据,这确实会改变结果。

  • 在第二个测试中,您创建一个新列表,使query仍然引用上一个列表。更改list不会更改query

  • 第三项测试仅将list归零,这对已经存在的参考query没有影响。