根据MS documentation,如果修改了基础枚举源,则枚举器应抛出InvalidOperationEx。当我直接从IEnumerable获取枚举器时,这可以工作。
问题:但是如果我从"查询数据结构中获取枚举器" ,然后修改源,然后调用MoveNext(),不抛出任何东西(参见代码)。
请考虑以下代码:
public static void Main(string[] args)
{
var src = new List<int>() { 1, 2, 3, 4 };
var q = src.Where(i => i % 2 == 1);
IEnumerable<int> nl = src;
var enmLinq = q.GetEnumerator();
var enmNonLinq = nl.GetEnumerator();
src.Add(5); //both enumerators should be invalid, as underlying data source changed
try
{
//throws as expected
enmNonLinq.MoveNext();
}
catch (InvalidOperationException)
{
Console.WriteLine("non LINQ enumerator threw...");
}
try
{
//DOES NOT throw as expected
enmLinq.MoveNext();
}
catch (InvalidOperationException)
{
Console.WriteLine("enumerator from LINQ threw...");
}
//It seems that if we want enmLinq to throw exception as expected:
//we must at least once call MoveNext on it (before modification)
enmLinq.MoveNext();
src.Add(6);
enmLinq.MoveNext(); // now it throws as it should
}
您似乎必须首先调用 MoveNext()方法,使其注意到底层源的更改。
为什么我认为这种情况正在发生: 我认为这是因为&#34;查询结构&#34;给你太懒惰的枚举器,而不是在 GetEnumerator()上初始化,而是在第一次调用 MoveNext()时初始化。
通过初始化,我的意思是将所有枚举器(来自 WhereEnumerable,SelectEnumerable 等LINQ方法返回的结构)连接到真正的底层数据结构。
问题: 我是对的还是我错过了什么? 你认为它是奇怪/错误的行为吗?
答案 0 :(得分:4)
你是对的。
在您GetEnumerator
返回的List<T>
上致电MoveNext
之前,LINQ查询不会在基础IEnumerable<T>
上调用Where
。
您可以在reference source中看到MoveNext
的实施方式如下:
public override bool MoveNext()
{
switch (state)
{
case 1:
enumerator = source.GetEnumerator();
state = 2;
goto case 2;
case 2:
while (enumerator.MoveNext())
{
TSource item = enumerator.Current;
if (predicate(item))
{
current = item;
return true;
}
}
Dispose();
break;
}
return false;
}
在&#39;初始&#39;状态(状态1),它将首先在GetEnumerator
上调用source
,然后再转到状态2.
答案 1 :(得分:2)
由于缺少更多详细信息,LINQ执行的查询可能会在第一次调用自己的GetEnumerator
时调用GetEnumerator
,或者尽可能晚地调用MoveNext
,例如第一次调用{ {1}}。
我不会假设任何特定行为。
实际上,实际实施(请参阅参考来源中的Enumerable.WhereEnumerableIterator<TSource>
)defers execution to the first call to MoveNext
。
答案 2 :(得分:1)
enmLinq
致电之前, MoveNext
尚未实现。因此,在调用src
之前对MoveNext
所做的任何修改都不会影响enmLinq
的有效性。在MoveNext
上致电enmLinq
后 - 枚举器已实现,因此src
上的任何更改都会导致后续MoveNext
来电异常。
答案 3 :(得分:-1)
您可以自己测试一下。
public static void Main(string[] args)
{
var src = new List<int>() { 1, 2, 3, 4 };
var q = src.Where(i =>
{
Output();
return i % 2 == 1;
}
);
IEnumerable<int> nl = src;
var enmLinq = q.GetEnumerator();
var enmNonLinq = nl.GetEnumerator();
src.Add(5); //both enumerators should be invalid, as underlying data source changed
try
{
//throws as expected
enmNonLinq.MoveNext();
}
catch (InvalidOperationException)
{
Console.WriteLine("non LINQ enumerator threw...");
}
try
{
//DOES NOT throw as expected
// Output() is called now.
enmLinq.MoveNext();
}
catch (InvalidOperationException)
{
Console.WriteLine("enumerator from LINQ threw...");
}
//It seems that if we want enmLinq to throw exception as expected:
//we must at least once call MoveNext on it (before modification)
enmLinq.MoveNext();
src.Add(6);
enmLinq.MoveNext(); // now it throws as it should
}
public static void Output()
{
Console.WriteLine("Test");
}
当你运行程序时,你会看到“Test”没有输出到控制台,直到你调用你的第一个MoveNext,这是在最初修改源之后发生的。