返回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。