Есть ли причина не использовать «возвращение дохода» при возврате IEnumerable? - PullRequest
21 голосов
/ 04 октября 2010

Простой пример - у вас есть метод или свойство, которое возвращает IEnumerable, и вызывающая сторона выполняет итерацию по нему в цикле foreach ().Должны ли вы всегда использовать 'возвращение дохода' в своем методе IEnumerable?Есть ли причина не делать этого?Хотя я понимаю, что это не всегда необходимо или даже «лучше» (может быть, это очень маленькая коллекция, например), есть ли причина активно избегать этого?

Часть кода, котораяя подумал, что эта функция, которую я написал, очень похожа на принятый ответ в этой теме - Как мне просмотреть диапазон дат?

Ответы [ 4 ]

23 голосов
/ 04 октября 2010

Блоки итераторов выполняют «живую» оценку при каждой итерации.

Иногда, однако, вы хотите, чтобы результаты были «снимком» в точкево время.В этих случаях вы, вероятно, не хотите использовать 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...
8 голосов
/ 04 октября 2010

Одна четкая причина не использовать перечислитель - это когда вам нужно IEnumerator<>.Reset() для работы.

Итераторы очень хороши, но они не могут избежать принципа "нет бесплатного обеда". Вы не найдете их в коде коллекции .NET Framework. Для этого есть веская причина: они не могут быть такими же эффективными, как выделенная реализация. Теперь, когда это имело значение для разработчиков .NET, они не могли предсказать, когда важна эффективность. Вы можете, вы знаете, находится ли ваш код на критическом пути вашей программы.

Итераторы немного в два раза медленнее, чем выделенная реализация. По крайней мере, это то, что я измерил, протестировав итератор List<>. Остерегайтесь микрооптимизаций, они все еще очень быстрые, и их большой Oh такой же.

Я включу тестовый код, чтобы вы могли убедиться в этом сами:

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(); }
}
4 голосов
/ 04 октября 2010

Странный вопрос.Если ваш метод возвращает IEnumerable, который он получает откуда-то еще, то, очевидно, он не будет использовать yield return.Если вашему методу нужно собрать конкретную структуру данных, представляющую результаты, чтобы выполнить некоторые манипуляции с ней перед возвратом, то, я думаю, вы тоже не будете использовать yield return там.

0 голосов
/ 04 октября 2010

Я так не думаю.Как предполагает @LBushkin, если бы вы собирались вернуть что-то в целом, вы бы вернули IList или что-то еще.Если вы возвращаете IEnumerable, люди ожидают отложенного выполнения, поэтому я думаю, что в этом случае вы всегда должны использовать yield.

...