Strange behaviour in linq c# under delayed execution
Hi, I have the following code which produces a strange behaviour. A property of an instance of objects contained in an IEnumerable produced by linq to Objects, does not get updated in subsequent foreach statements. The foreach statement should enuemerate the IEnumerable. Instead the solution is to enum开发者_如何学编程erate it before.
Although I found the solution I have not seen this documented anywhere either in books or articles, dealing with similar examples. Perhaps somebody with intricate knowledge of linq can explain it.
It took me a day to pinpoint the exact cause of the error, and is not easy to debug in a large application. I then reproduced it in a much simpler environment, presented below.
public class MyClass
{
public int val ;
}
public class MyClassExtrax
{
public MyClass v1 { get; set; }
public int prop1 { get; set; }
}
void Main()
{
List <MyClass> list1 = new List<MyClass>();
MyClass obj1 = new MyClass(); obj1.val = 10;
list1.Add(obj1);
MyClass obj2 = new MyClass();
obj2.val = 10;
list1.Add(obj2);
IEnumerable<MyClassExtrax> query1 =
from v in list1
where v.val >= 0
select new MyClassExtrax{ v1=v , prop1=0 } ;
//query1=query1.ToList(); solves the problem..but why is this needed..?
foreach (MyClassExtrax fj in query1)
{
fj.v1.val = 40;
fj.prop1 = 40; //property does not get updated..
}
foreach (MyClass obj in list1)
{
Console.WriteLine("in list 1 value is {0} : ", obj.val);
}
foreach (MyClassExtrax obj in query1)
{
Console.WriteLine("in MyClassExtra list v1.val is {0}, prop1 is {1} ", obj.v1.val, obj.prop1);
}
}
output: in list 1 value is 40 :
in list 1 value is 40 :
in MyClassExtra list v1.val is 40, prop1 is 0
in MyClassExtra list v1.val is 40, prop1 is 0
As you can see prop1 does not get updated to 40.!!
This is quite simple, it's well-documented. This is because of the deferred execution feature of LINQ. This code
IEnumerable<MyClassExtrax> query1 =
from v in list1
where v.val >= 0
select new MyClassExtrax{ v1=v , prop1=0 } ;
doesn't actually create the objects. It just creates an object that when iterated over actually creates the object. That is, you can think of query1
as the rule that knows how to spit out the objects when they are requested. So when you do this:
foreach (MyClassExtrax fj in query1)
{
fj.v1.val = 40;
fj.prop1 = 40; //property does not get updated..
}
and then this:
foreach (MyClassExtrax obj in query1)
{
Console.WriteLine("in MyClassExtra list v1.val is {0}, prop1 is {1} ", obj.v1.val, obj.prop1);
}
you are executing the rule for generating the objects twice. That is, two distinct sequences of objects are produced, and they are not shared between the sequences. That is why you don't see the values updated; the references across the two iterations of the sequence are not the same.
However, when you call ToList
, and then walk the resulting list, now you have only produced one sequence of objects, and that sequence of objects is obviously the same across the two iterations.
精彩评论