Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Strange behaviour in linq c# under delayed execution

Tags:

c#

linq

deferred

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 enumerate 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.!!

like image 219
gregor Avatar asked Sep 03 '11 16:09

gregor


1 Answers

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.

like image 82
jason Avatar answered Sep 18 '22 17:09

jason