Что приводит к тому, что этот список передается по ссылке при вызове в одном направлении, но по значению другой?

Я делал простой тест для запуска метода проверки и сталкивался с этой странной ситуацией.

public IEnumerable<int> ints (List<int> l)
{
 if(false)yield return 6;
 l.Add(4);
}


void Main()
{
 var a = new List<int>();
 var b = new List<int>();
 for( int i = 0; i < 4; i++ ){
  a.Add(i);
  b.Add(i);
 }
 a.AddRange(ints(a));
 ints(b);
 Console.WriteLine(a);
 Console.WriteLine(b);
}

Как только этот код будет запущен, a будет содержать [0,1,2,3,4]. Однако b будет содержать [0,1,2,3]. Почему вызов метода в качестве аргумента в AddRange разрешил передачу списка по ссылке? Или, если этого не произошло, что же?

Ответ 1

ints(b) вызов не перечисляет IEnumerable, поэтому код никогда не достигает строки l.Add(4), в отличие от случая AddRange, который перечисляет все элементы, чтобы добавить их в список.

Чтобы увидеть, что он вызвал для случая b, перечислить результат вручную:

ints(b).ToList();

IEnumerable<T>, реализованный через функции, не выполняет тело функции до начала перечисления - код фактически преобразуется компилятором в класс с состояниями для поддержки истинной ленивой оценки перечислимого (подробности могут быть найдены в нескольких статьях, т.е. Iterator Pattern demystified - ссылка предоставлена ​​Тимом Шмельтером).