Разное поведение между Join и SelectMany после замены одного из наборов - PullRequest
2 голосов
/ 27 декабря 2010

Я надеюсь, что кто-то может пролить свет на (для меня) неожиданную поведенческую разницу между двумя (с точки зрения результата) равными запросами.
Маленькая программа может стоить тысячи слов, так что вот:

static void Main(string[] args)  
{  
  var l1 = new List<int> { 1, 2, 3 };  
  var l2 = new List<int> { 2, 3, 4 };  

  var q1 =  // or var q1 = l1.Join(l2, i => i, j => j, (i, j) => i);
      from i in l1  
      join j in l2
      on i equals j
      select i; 

  var q2 = //or var q2 = l1.SelectMany(i => l2.Where(j => i == j));  
      from i in l1
      from j in l2
      where i == j
      select i;

  var a1 = q1.ToList(); // 2 and 3, as expected
  var a2 = q2.ToList(); // 2 and 3, as expected

  l2.Remove(2);

  var b1 = q1.ToList(); // only 3, as expected  
  var b2 = q2.ToList(); // only 3, as expected

  // now here goes, lets replace l2 alltogether.   
  // Afterwards, I expected the same result as q1 delivered...  

  l2 = new List<int> { 2, 3, 4 };

  var c1 = q1.ToList(); // only 3 ? Still using the previous reference to l2 ?  
  var c2 = q2.ToList();  // 2 and 3, as expected  
}

Теперь я знаю, что Join внутренне использует класс поиска для оптимизации производительности, и без особых знаний, я предполагаю, что сочетание этого с захваченными переменными может вызвать такое поведение, но сказать, что я действительно это понимаю, нет: - )
Это пример того, что Джоэл называет «дырявой абстракцией»?

Ура, Bart

1 Ответ

2 голосов
/ 27 декабря 2010

Вы на самом деле почти там, учитывая ваши расширения запроса в комментариях:

var q1 = l1.Join(l2, i => i, j => j, (i, j) => i);

var q2 = l1.SelectMany(i => l2.Where(j => i == j));

Посмотрите, где l2 используется в каждом случае. В случае Join значение l2 сразу передается в метод. (Помните, что значение является ссылкой на список, хотя ... изменение содержимого списка не то же самое, что изменение значения l2.) Изменение значения l2 позже не влияет на то, что Запоминается запрос, возвращаемый методом Join.

Теперь посмотрите на SelectManay: l2 используется только в лямбда-выражении ... так что это захваченная переменная . Это означает, что всякий раз, когда вычисляется лямбда-выражение, используется значение l2 в тот момент времени ... поэтому оно будет отражать любые изменения значения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...