4

呃,不太清楚如何表达这个但是..

给定一个使用 yield return 创建的 IEnumerable,包含一个类的三个实例,为什么调用 .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 表现相同并像这样输出......

IEnumerable<Thing>
Updated thing 1 updated True 45592480
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

我错过了什么?!

4

4 回答 4

3

该方法GetThings不返回真正的集合。它返回一个“食谱”如何“烹饪”一个集合,并且只有当你要求迭代它时它才会“烹饪”。这就是 的魔力yield

因此,每次您调用.First()循环时,实际上都会创建新实例。

于 2011-10-11T16:05:03.517 回答
1

由“yield return”构造的 IEnumerable 仅在枚举时产生值,并且在第一种情况下枚举它两次。当您第二次枚举它时,您实际上是在创建一组完全独立的事物。

Yield return 基本上是生成状态机的代码的快捷方式,当枚举状态机时,它会遍历代码以生成枚举结果。结果本身不会保存在任何地方,直到您对它们执行某些操作,例如将它们放入列表中。

于 2011-10-11T16:04:41.983 回答
0

迭代器(任何使用 的方法yield return)在迭代它们时都会进行惰性求值。这意味着您的方法主体在您调用它时不会执行 - 它仅在您枚举结果时执行IEnumerable<T>,通过调用foreach或诸如此类。每次你执行它foreach

由于.First()必须枚举IEnumerable<T>(因为这是从中获取元素的唯一方法),因此每次调用.First().

.ToList()通常的解决方案是通过调用or来强制迭代器在您准备好时运行.ToArray()。这会给你一个List<T>或一个数组,当你迭代它时它不会再改变。

于 2011-10-11T16:05:44.717 回答
0

每次迭代时,您的 IEnumerable 实现 (GetThings) 都会返回新项目。因此,在 IEnumerable 的 foreach 中,每个 Thing 都是新创建的。当您将 ToList 添加到您的 IEnumerable 时,列表确实包含从 IEnumerable 产生的每个项目的“副本”,因为您的 IEnumerable 创建的“事物”被保存到列表中。对“事物”列表的后续迭代将始终产生相同的“事物”集。对 IEnumerable 的后续迭代将始终产生一组新的“事物”。

于 2011-10-11T16:07:25.310 回答