当不使用收益(回报)

这个问题在这里已经有了答案:
返回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>直到需要的任何好处。

    链接地址: http://www.djcxy.com/p/9103.html

    上一篇: When NOT to use yield (return)

    下一篇: Why use the yield keyword, when I could just use an ordinary IEnumerable?