Есть ли причина не использовать "возвращение дохода" при возврате IEnumerable?
Простой пример - у вас есть метод или свойство, которое возвращает IEnumerable, и вызывающая сторона выполняет итерацию по нему в цикле foreach(). Должны ли вы всегда использовать 'yield return' в своем методе IEnumerable? Есть ли причина не делать этого? Хотя я понимаю, что это не всегда необходимо или даже "лучше" (может быть, это очень маленькая коллекция, например), есть ли причина активно избегать этого?
Часть кода, которая заставила меня задуматься об этом, была написанной мной функцией, очень похожей на принятый ответ в этой теме - Как я могу просмотреть диапазон дат?
4 ответа
Блоки итераторов выполняют "живую" оценку при каждой итерации.
Иногда, однако, вы хотите, чтобы результаты были "моментальным снимком" в определенный момент времени. В этих случаях вы, вероятно, не хотите использовать 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 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(); }
}
Странный вопрос. Если ваш метод возвращает IEnumerable
что он получает откуда-то еще, то, очевидно, он не будет использовать yield return
, Если вашему методу нужно собрать конкретную структуру данных, представляющую результаты, чтобы выполнить некоторые манипуляции с ней перед возвратом, то, я думаю, вы не будете использовать yield return
там тоже.
Не читая ответов, я скажу вам, что использование yield в службе WCF испортит ваше исключение FaultException!
Мне потребовалось некоторое время, чтобы выяснить, что виной всему урожай. Мне просто нужен был быстрый способ запустить некоторые ошибки. Разумеется, правильным способом было бы включить в ответ объект Error, но мне это нужно было быстро. Я сделал: выбросил новое исключение FaultException("Нет подписки с идентификатором: " + subscriptionId.ToString());
Но использование yield, даже ниже фактического выброса, вызвало обычное исключение, сеть
Я так не думаю. Как предполагает @LBushkin, если бы вы собирались вернуть что-то целиком, вы бы вернули IList или что-то еще. Если вы возвращаете IEnumerable, люди ожидают отложенного выполнения, поэтому я думаю, что вы всегда должны использовать yield в этом случае.