我以为我对IEnumerable<T>
了如指掌,但遇到了一个我无法解释的情况。当我们在IEnumerable
上调用linq方法时,将执行推迟到枚举对象之前,是吧?
那么如何解释以下示例:
public class CTest
{
public CTest(int amount)
{
Amount = amount;
}
public int Amount { get; set; }
public override string ToString()
{
return $"Amount:{Amount}";
}
public static IEnumerable<CTest> GenerateEnumerableTest()
{
var tab = new List<int> { 2, 5, 10, 12 };
return tab.Select(t => new CTest(t));
}
}
到目前为止,还不错!
尽管我对IEnumerable<T>
和.Where
linq方法有一定的了解,但以下测试却给了我一个意外的结果:
[TestMethod]
public void TestCSharp()
{
var tab = CTest.GenerateEnumerableTest();
foreach (var item in tab.Where(i => i.Amount > 6))
{
item.Amount = item.Amount * 2;
}
foreach (var t in tab)
{
var s = t.ToString();
Debug.Print(s);
}
}
选项卡中的任何项目都不会乘以2。输出将是: 数量:2 数量:5 数量:10 数量:12
任何人都可以解释为什么枚举制表符后获得原始值。
当然,在调用.ToList()
方法之后,调用GenerateEnumerableTest()
后一切正常。
答案 0 :(得分:9)
var tab = CTest.GenerateEnumerableTest();
此tab
是LINQ查询,它生成CTest
个实例,这些实例是根据int
值初始化的,这些值来自一个永不改变的整数数组。因此,无论何时要求此查询,您都将获得“相同”实例(具有原始Amount
)。
如果要“具体化”此查询,可以使用ToList
,然后进行更改。
否则,您将修改仅在第一个CTest
循环中存在的foreach
个实例。第二个循环枚举未经修改的CTest
的其他Amount
实例。
因此查询包含如何获取商品的信息,您也可以直接调用该方法:
foreach (var item in CTest.GenerateEnumerableTest().Where(i => i.Amount > 6))
{
item.Amount = item.Amount * 2;
}
foreach (var t in CTest.GenerateEnumerableTest())
{
// now you don't expect them to be changed, do you?
}
答案 1 :(得分:1)
与许多LINQ操作一样,Select
是惰性的,并且使用延迟执行,因此您的lambda表达式不会被执行,因为您正在调用Select
但从未使用结果。这就是为什么在调用.ToList()
方法之后调用GenerateEnumerableTest()
后一切正常的原因:
var tab = CTest.GenerateEnumerableTest().ToList();