简单示例 - 您有一个返回IEnumerable的方法或属性,调用者在foreach()循环中迭代它。你总是在你的IEnumerable方法中使用'yield return'吗?有没有理由不去?虽然我知道它可能并不总是有必要,甚至“更好”(例如,它可能是一个非常小的集合),是否有理由主动避免这样做?
让我思考这个问题的代码是我写的一个函数,非常类似于这个帖子中接受的答案 - How do I loop through a date range?
答案 0 :(得分:23)
迭代器块每次迭代时都会执行“实时”评估。
但是,有时候,您想要的行为是将结果作为某个时间点的“快照”。在这些情况下,您可能不想使用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...
答案 1 :(得分:8)
不使用枚举器的一个明显原因是当您需要IEnumerator<>.Reset()
工作时。
迭代器非常很好,但他们无法摆脱“没有免费午餐”的原则。您将不会在.NET框架集合代码中找到它们。这是有充分理由的,它们不能像专用实现那样高效。现在这对.NET设计师来说很重要,他们无法预测效率何时重要。您可以,您知道您的代码是否在您的程序的关键路径中。
迭代器的速度比专用实现慢两倍。至少这是我通过测试List<>
迭代器测量的。注意微观优化,他们仍然非常快,他们的大哦也是一样。
我将包含测试代码,以便您自行验证:
using System;
using System.Collections.Generic;
using System.Diagnostics;
class Program {
static void Main(string[] args) {
var lst = new MyList<int>();
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<T> : IList<T> {
private List<T> lst = new List<T>();
public IEnumerable<T> 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<T>)lst).IsReadOnly; } }
public bool Remove(T item) { return lst.Remove(item); }
public IEnumerator<T> GetEnumerator() { return lst.GetEnumerator(); }
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); }
}
答案 2 :(得分:4)
奇怪的问题。如果您的方法返回的是从其他地方获取的IEnumerable
,那么显然它不会使用yield return
。如果你的方法需要组装一个表示结果的具体数据结构,以便在返回之前对它进行一些操作,那么我猜你也不会在那里使用yield return
。
答案 3 :(得分:0)
我不这么认为。正如@LBushkin建议的那样,如果你要作为一个整体返回一些东西,你会返回一个IList或其他什么。如果你要返回一个IEnumerable,那么人们期望延迟执行,所以我认为你应该总是使用yield。
答案 4 :(得分:0)
如果不仔细阅读答案,我会告诉您,在WCF服务中使用yield会弄乱您的FaultException!
我花了很多时间才发现产量是罪魁祸首。我只需要一种快速的方法就可以使运行中出现som错误。正确的方法当然是在回复中包含一个Error对象,但是我需要很快。 我做了: 抛出新的FaultException(“无ID的订阅:” + subscriptionId.ToString());
但是使用收益率,甚至低于实际投掷率,都会导致常规异常,网络