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

时间:2010-10-04 15:23:52

标签: c# ienumerable yield-return

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

让我思考这个问题的代码是我写的一个函数,非常类似于这个帖子中接受的答案 - How do I loop through a date range?

5 个答案:

答案 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());

但是使用收益率,甚至低于实际投掷率,都会导致常规异常,网络