当不使用收益(回报)
这个问题在这里已经有了答案:
返回IEnumerable时,有没有理由不使用“yield return”?
关于yield return
的好处,这里有几个有用的问题。 例如,
有人可以揭开yield关键字的神秘面纱吗?
有趣的使用C#收益
关键词
什么是yield关键字
我正在寻找什么时候不使用yield return
想法。 例如,如果我期望需要返回一个集合中的所有项目,那么yield
似乎不会有用,对吧?
有什么情况下使用yield
会受到限制,不必要的,让我陷入困境,否则应该避免?
有什么情况下使用收益率会受到限制,不必要的,让我陷入困境,否则应该避免?
在处理递归定义的结构时,仔细考虑使用“收益回报”是一个好主意。 例如,我经常看到这个:
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”将使O(h)嵌套调用到MoveNext。 因为它对具有n个项目的树执行O(n)次,所以使得算法O(hn)。 并且由于二叉树的高度为lg n <= h <= n,这意味着该算法在时间上最多为O(n lg n),最差为O(n ^ 2),并且最佳情况下O(lg n),最坏的情况是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)是堆空间。
进一步阅读:请参阅Wes Dyer关于此主题的文章:
http://blogs.msdn.com/b/wesdyer/archive/2007/03/23/all-about-iterators.aspx
有什么情况下使用收益率会受到限制,不必要的,让我陷入困境,否则应该避免?
我可以想到几个例子,IE:
返回现有迭代器时避免使用yield return。 例:
// 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;
}
如果您不希望延迟该方法的执行代码,请避免使用yield return。 例:
// 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);
}
要实现的关键是yield
是有用的,然后你可以决定哪些案例不从中受益。
换句话说,当你不需要序列进行懒惰评估时,你可以跳过yield
的使用。 那是什么时候? 当你不介意将你的整个收藏品放在记忆中时,这将是一件好事。 否则,如果你有一个巨大的序列会对记忆产生负面影响,你会希望使用yield
逐步处理它(即,懒惰地)。 比较两种方法时,分析器可能会派上用场。
注意大多数LINQ语句如何返回一个IEnumerable<T>
。 这使我们能够不断将不同的LINQ操作串联在一起,而不会对每个步骤的性能产生负面影响(也称为延迟执行)。 替代图片将在每个LINQ语句之间放置一个ToList()
调用。 这将导致在执行下一个(链接的)LINQ语句之前立即执行每个前面的LINQ语句,从而放弃延迟评估和利用IEnumerable<T>
直到需要的任何好处。
上一篇: When NOT to use yield (return)
下一篇: Why use the yield keyword, when I could just use an ordinary IEnumerable?