Почему при вызове Enumerable.First () возвращается копия первого элемента в перечислении - PullRequest
4 голосов
/ 11 октября 2011

Э-э, не совсем уверен, как это сформулировать, но ..

Учитывая IEnumerable, созданный с использованием yield return, содержащий три экземпляра класса, почему вызов .First (), кажется, возвращает «копию»первого экземпляра?

См. следующий код;

public class Thing
    {
        public bool Updated { get; set; }

        public string Name { get; private set; }

        public Thing(string name)
        {
            Name = name;
        }

        public override string ToString()
        {
            return string.Format("{0} updated {1} {2}", Name, Updated, GetHashCode());
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("IEnumerable<Thing>");
            var enumerableThings = GetThings();
            var firstThing = enumerableThings.First();
            firstThing.Updated = true;
            Console.WriteLine("Updated {0}", firstThing);
            foreach (var t in enumerableThings)
                Console.WriteLine(t);

            Console.WriteLine("IList<Thing>");
            var thingList = GetThings().ToList();
            var thing1 = thingList.First();
            thing1.Updated = true;
            Console.WriteLine("Updated {0}", thing1);
            foreach (var t in thingList)
                Console.WriteLine(t);

            Console.ReadLine();
        }

        private static IEnumerable<Thing> GetThings()
        {
            for (int i = 1; i <= 3; i++)
            {
                yield return new Thing(string.Format("thing {0}", i));
            }
        }
    }
}

при выполнении этого выдает следующий вывод:

IEnumerable<Thing>
Updated thing 1 updated True 37121646
thing 1 updated False 45592480
thing 2 updated False 57352375
thing 3 updated False 2637164
IList<Thing>
Updated thing 1 updated True 41014879
thing 1 updated True 41014879
thing 2 updated False 3888474
thing 3 updated False 25209742

но я бы ожидал, что IList и IEnmerable будут вести себя одинаково и будут выводить как этот ...

Чего мне не хватает?!

Ответы [ 4 ]

3 голосов
/ 11 октября 2011

Метод GetThings не не возвращает реальную коллекцию.Он возвращает «рецепт», как «готовить» коллекцию, и он «готовится» только тогда, когда вы просите повторить его.В этом магия yield.

Так что каждый раз, когда вы вызываете .First(), цикл работает, и действительно, создается новый экземпляр.

1 голос
/ 11 октября 2011

IEnumerable, созданный методом yield return, генерирует значения только при его перечислении, и вы перечисляете его дважды в первом случае. Вы фактически создаете совершенно отдельный набор вещей, когда перечисляете его во второй раз.

Возвращение доходности - это, по сути, сокращение для кода, который генерирует конечный автомат, который при перечислении перемещается по коду для получения перечисляемых результатов. Сами результаты не сохраняются нигде, пока вы не сделаете с ними что-то, например, поместите их в список.

0 голосов
/ 11 октября 2011

Ваша реализация IEnumerable (GetThings) возвращает новые элементы при каждой итерации. Итак, в предисловии IEnumerable каждая вещь создается заново. Когда вы добавляете ToList в ваш IEnumerable, список действительно содержит «копию» каждого элемента, полученного из IEnumerable, в том смысле, что «вещи», созданные вашим IEnumerable, сохраняются в списке. Последующие итерации по списку «вещей» всегда будут приводить к одному и тому же набору «вещей». Последующие итерации над IEnumerable всегда будут давать новый набор «вещей».

0 голосов
/ 11 октября 2011

Итераторы (любой метод, который использует yield return) оцениваются ленивыми при их итерации.Это означает, что тело вашего метода не выполняется, когда вы вызываете его - оно выполняется только тогда, когда вы перечислите результирующий IEnumerable<T>, через вызов foreach или что-то подобное.И он выполняет каждый раз, когда вы foreach.

Так как .First() должен перечислять IEnumerable<T> (так как это единственный способ извлечь из него элементы), тело вашего методаперезапускается каждый раз, когда вы вызываете .First().

Обычное решение - заставить итератор запускаться в какой-то момент, когда вы будете готовы, вызывая .ToList() или .ToArray().Это даст вам List<T> или массив, который больше не будет меняться при его повторении.

...