Странное поведение в linq c # при отложенном выполнении - PullRequest
6 голосов
/ 03 сентября 2011

Привет, у меня есть следующий код, который производит странное поведение.Свойство экземпляра объектов, содержащихся в IEnumerable, созданного linq to Objects, не обновляется в последующих операторах foreach.Оператор foreach должен перечислять IEnumerable.Вместо этого решение состоит в том, чтобы перечислить его раньше.

Хотя я нашел решение, я нигде не видел этого ни в книгах, ни в статьях, имеющих дело с подобными примерами.Возможно, кто-то со сложным знанием linq может объяснить это.

Мне потребовался день, чтобы точно определить причину ошибки, и отладка в большом приложении не так проста.Затем я воспроизвел его в гораздо более простой среде, представленной ниже.

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); 
  }

 }

вывод: в списке 1 значение равно 40:

в списке 1 значение равно 40:

в списке MyClassExtra v1.val равен 40, prop1 равен 0

в списке MyClassExtra v1.val равен 40, prop1 равен 0

Как видно, prop1 не обновляется до 40. !!

1 Ответ

7 голосов
/ 03 сентября 2011

Это довольно просто, это хорошо документировано . Это связано с функцией отложенного выполнения LINQ. Этот код

IEnumerable<MyClassExtrax> query1 =
 from v in list1
 where v.val >= 0
 select new MyClassExtrax{ v1=v ,  prop1=0 } ;

на самом деле не создает объекты. Он просто создает объект, который при повторном выполнении фактически создает объект. То есть вы можете думать о query1 как о правиле, которое знает, как выплевывать объекты при запросе . Поэтому, когда вы делаете это:

foreach (MyClassExtrax fj in query1)
{
    fj.v1.val = 40;
    fj.prop1 = 40;   //property does not get updated..
}

, а затем это:

foreach (MyClassExtrax obj in query1)
{
   Console.WriteLine("in MyClassExtra list v1.val is {0}, prop1 is {1}  ", obj.v1.val, obj.prop1); 
}

вы выполняете правило для генерации объектов дважды. То есть создаются две отдельные последовательности объектов, и они не разделяются между последовательностями. Вот почему вы не видите обновленные значения; ссылки на две итерации последовательности не совпадают.

Однако, когда вы вызываете ToList и затем просматриваете получившийся список, теперь вы создали только одну последовательность объектов, и эта последовательность объектов, очевидно, одинакова на протяжении двух итераций.

...