返回IEnumerable时是否有理由不使用’yield return’?

简单的例子 – 你有一个返回IEnumerable的方法或属性,调用者在foreach()循环中迭代它。 你应该总是在你的IEnumerable方法中使用’yield return’吗? 有没有理由不去? 虽然我知道它可能并不总是有必要,甚至“更好”(例如,它可能是一个非常小的集合),是否有理由主动避免这样做?

让我思考这个问题的代码是我写的一个函数非常类似于这个post中接受的答案 – 我如何遍历日期范围?

迭代器块在每次迭代时执行“实时”评估。

但是,有时候,您想要的行为是将结果作为某个时间点的“快照”。 在这些情况下,您可能不希望使用yield return ,而是返回List<>Set ,或者其他一些持久集合。

如果直接处理查询对象,也不必使用yield return 。 LINQ查询通常就是这种情况 – 最好只从查询中返回IEnumerable<>而不是迭代并自己yield return结果。 例如:

 var result = from obj in someCollection where obj.Value < someValue select new { obj.Name, obj.Value }; foreach( var item in result ) yield return item; // THIS IS UNNECESSARY.... // just return {result} instead... 

不使用枚举器的一个明显原因是当你需要IEnumerator<>.Reset()工作时。

迭代器非常好,但他们无法摆脱“没有免费午餐”的原则。 您将不会在.NET框架集合代码中找到它们。 这是有充分理由的,它们不能像专用实现那样高效。 现在这对.NET设计师来说很重要,他们无法预测效率何时重要。 您可以,您知道您的代码是否在程序的关键路径中。

迭代器的速度比专用实现慢两倍。 至少这是我通过测试List<>迭代器测量的。 注意微观优化,他们仍然非常快,他们的大哦也是一样。

我将包含测试代码,以便您自己validation:

 using System; using System.Collections.Generic; using System.Diagnostics; class Program { static void Main(string[] args) { var lst = new MyList(); for (int ix = 0; ix < 10000000; ++ix) lst.Add(ix); for (int test = 0; test < 20; ++test) { var sw1 = Stopwatch.StartNew(); foreach (var item in lst) ; sw1.Stop(); var sw2 = Stopwatch.StartNew(); foreach (var item in lst.GetItems()) ; sw2.Stop(); Console.WriteLine("{0} {1}", sw1.ElapsedMilliseconds, sw2.ElapsedMilliseconds); } Console.ReadLine(); } } class MyList : IList { private List lst = new List(); public IEnumerable GetItems() { foreach (T item in lst) yield return item; } public int IndexOf(T item) { return lst.IndexOf(item); } public void Insert(int index, T item) { lst.Insert(index, item); } public void RemoveAt(int index) { lst.RemoveAt(index); } public T this[int index] { get { return lst[index]; } set { lst[index] = value; } } public void Add(T item) { lst.Add(item); } public void Clear() { lst.Clear(); } public bool Contains(T item) { return lst.Contains(item); } public void CopyTo(T[] array, int arrayIndex) { lst.CopyTo(array, arrayIndex); } public int Count { get { return lst.Count; } } public bool IsReadOnly { get { return ((IList)lst).IsReadOnly; } } public bool Remove(T item) { return lst.Remove(item); } public IEnumerator GetEnumerator() { return lst.GetEnumerator(); } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } } 

奇怪的问题。 如果你的方法返回一个从其他地方获得的IEnumerable ,那么显然它不会使用yield return 。 如果你的方法需要组装一个表示结果的具体数据结构,以便在返回之前对它进行一些操作,那么我猜你也不会在那里使用yield return

我不这么认为。 正如@LBushkin建议的那样,如果你要作为一个整体返回一些内容,你将返回一个IList或其他什么。 如果你要返回一个IEnumerable,人们期望延迟执行,所以我认为你应该总是使用yield。