关于c#:在返回IEnumerable时是否有理由不使用’yield return’?

Is there ever a reason to not use 'yield return' when returning an IEnumerable?

简单示例-您有一个方法或属性返回IEnumerable,调用方正在foreach()循环中迭代该方法或属性。在IEnumerable方法中应该始终使用"yield return"吗?有没有理由不这么做?虽然我知道这可能并不总是必要的,甚至"更好"(例如,它可能是一个非常小的集合),但有没有理由积极避免这样做?

让我想到这一点的代码是我写的一个函数,它非常类似于这个线程中可接受的答案——我如何循环访问一个日期范围?


迭代器块每次迭代时都执行"实时"评估。

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

如果直接处理查询对象,也不需要使用yield return。对于LINQ查询,通常是这样——最好从查询中返回IEnumerable<>,而不是自己迭代和yield returning结果。例如:

1
2
3
4
5
6
7
8
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<>迭代器所测量的。当心微优化,他们仍然非常快,他们的大哦是一样的。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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(); }
}


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


我不这么认为。正如@lbushkin建议的那样,如果您打算整体返回某个东西,那么您将返回一个IList或其他什么东西。如果您返回的是IEnumerable,那么人们希望延迟执行,因此我认为在这种情况下应该始终使用yield。