// Don't do this, it creates overhead for no reason
// (a new state machine needs to be generated)
public IEnumerable<string> GetKeys()
{
foreach(string key in _someDictionary.Keys)
yield return key;
}
// DO this
public IEnumerable<string> GetKeys()
{
return _someDictionary.Keys;
}
Avoid using yield return when you don't want to defer execution code for the method. Example:
// Don't do this, the exception won't get thrown until the iterator is
// iterated, which can be very far away from this method invocation
public IEnumerable<string> Foo(Bar baz)
{
if (baz == null)
throw new ArgumentNullException();
yield ...
}
// DO this
public IEnumerable<string> Foo(Bar baz)
{
if (baz == null)
throw new ArgumentNullException();
return new BazIterator(baz);
}
public static IEnumerable<T> PreorderTraversal<T>(Tree<T> root)
{
if (root == null) yield break;
yield return root.Value;
foreach(T item in PreorderTraversal(root.Left))
yield return item;
foreach(T item in PreorderTraversal(root.Right))
yield return item;
}
看起来非常合理的代码,但是存在性能问题。假设树很深。然后最多构建 O (h)嵌套迭代器。在外部迭代器上调用“ MoveNext”,然后对 MoveNext 进行 O (h)嵌套调用。因为它对带有 n 个条目的树执行 O (n)次,所以算法是 O (hn)。由于二叉树的高度是 lgn < = h < = n,这意味着该算法在时间上最好为 O (nlgn) ,最差为 O (n ^ 2) ,在堆栈空间上最好为 O (lgn) ,最差为 O (n)。在堆空间中是 O (h) ,因为每个枚举数都是在堆上分配的。(关于 C # 的实现,我知道; 符合要求的实现可能具有其他堆栈或堆空间特征。)
但是迭代一棵树在时间上可以是 O (n) ,在堆栈空间上可以是 O (1)。你可以这样写:
public static IEnumerable<T> PreorderTraversal<T>(Tree<T> root)
{
var stack = new Stack<Tree<T>>();
stack.Push(root);
while (stack.Count != 0)
{
var current = stack.Pop();
if (current == null) continue;
yield return current.Value;
stack.Push(current.Left);
stack.Push(current.Right);
}
}
它仍然使用收益率回报,但在这方面要聪明得多。现在我们在时间上是 O (n) ,在堆空间上是 O (h) ,在堆空间上是 O (1)。
IEnumerable<T> GetMyStuff() {
foreach (var x in MyCollection)
if (...)
yield return (...);
}
如果 MyCollection 有可能因为调用者所做的某些事情而发生变化,那么这将是危险的:
foreach(T x in GetMyStuff()) {
if (...)
MyCollection.Add(...);
// Oops, now GetMyStuff() will throw an exception
// because MyCollection was modified.
}