Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/337.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 何时不使用收益率(收益率)_C#_.net_Yield_Yield Return - Fatal编程技术网

C# 何时不使用收益率(收益率)

C# 何时不使用收益率(收益率),c#,.net,yield,yield-return,C#,.net,Yield,Yield Return,这个问题在这里已经有了答案: 关于收益率回报的好处,这里有几个有用的问题。比如说, 我正在寻找关于何时不使用收益率的想法。例如,如果我希望返回集合中的所有项,那么yield似乎不是很有用,对吗 在什么情况下使用yield会受到限制、不必要、给我带来麻烦,或者应该避免使用其他方法?当您不希望代码块返回迭代器以顺序访问基础集合时,您不需要yield return。您只需返回集合即可 要认识到的关键是yield对什么有用,然后你可以决定哪些案例没有从中受益 换句话说,当不需要对序列进行惰性

这个问题在这里已经有了答案:

关于
收益率回报的好处,这里有几个有用的问题。比如说,

我正在寻找关于何时不使用
收益率
的想法。例如,如果我希望返回集合中的所有项,那么
yield
似乎不是很有用,对吗


在什么情况下使用
yield
会受到限制、不必要、给我带来麻烦,或者应该避免使用其他方法?

当您不希望代码块返回迭代器以顺序访问基础集合时,您不需要
yield return
。您只需
返回
集合即可

要认识到的关键是
yield
对什么有用,然后你可以决定哪些案例没有从中受益

换句话说,当不需要对序列进行惰性评估时,可以跳过使用
yield
。什么时候?当你不介意马上把你的全部收藏都记在记忆里的时候。否则,如果您有一个会对内存产生负面影响的庞大序列,您可能希望使用
yield
一步一步地(即,惰性地)处理它。在比较这两种方法时,分析器可能会派上用场

注意大多数LINQ语句如何返回
IEnumerable
。这使我们能够连续地将不同的LINQ操作串在一起,而不会对每个步骤的性能产生负面影响(也称为延迟执行)。另一种方法是在每个LINQ语句之间插入一个
ToList()
调用。这将导致在执行下一个(链接的)LINQ语句之前立即执行前面的每个LINQ语句,从而放弃延迟求值的任何好处,并在需要时使用
IEnumerable

使用收益率的情况有哪些 将是有限的,不必要的,抓住我 陷入困境,否则应该 避免

我能想到几个案例,即:

  • 在返回现有迭代器时,避免使用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;
    }
    
    // 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);
    }
    
    //不要这样做,它会无缘无故地产生开销
    //(需要生成一个新的状态机)
    公共IEnumerable GetKeys()
    {
    foreach(字符串键在_someDictionary.Keys中)
    屈服返回键;
    }
    //这样做
    公共IEnumerable GetKeys()
    {
    返回_someDictionary.Keys;
    }
    
  • 当您不想延迟方法的执行代码时,请避免使用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;
    }
    
    // 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);
    }
    
    //不要这样做,直到迭代器被调用,才会抛出异常
    //迭代,这可能离此方法调用很远
    公共IEnumerable Foo(酒吧)
    {
    if(baz==null)
    抛出新ArgumentNullException();
    产量
    }
    //这样做
    公共IEnumerable Foo(酒吧)
    {
    if(baz==null)
    抛出新ArgumentNullException();
    返回新的baz迭代器(baz);
    }
    

当您需要随机访问时,产量将是有限的/不必要的。如果您需要访问元素0,然后访问元素99,那么就基本上消除了延迟求值的用处。

如果您定义了一个Linq-y扩展方法,其中包装了实际的Linq成员,那么这些成员通常会返回迭代器。通过迭代器自己让步是不必要的


除此之外,使用yield来定义一个基于JIT的“流式”可枚举项也不会有太大的麻烦。

一个可能会让你大吃一惊的是,如果你正在序列化枚举结果并通过网络发送它们。因为执行被推迟到需要结果时,所以您将序列化一个空枚举并将其发回,而不是返回您想要的结果

在哪些情况下,收益率的使用会受到限制、不必要、给我带来麻烦,或者应该避免使用其他方法

在处理递归定义的结构时,仔细考虑使用“收益-回报”是一个好主意。例如,我经常看到:

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;
}
公共静态IEnumerable PreorderTraversal(树根)
{
如果(root==null)产生中断;
收益率返回根值;
foreach(PreorderTraversal(root.Left))中的T项
收益回报项目;
foreach(PreorderTraversal中的T项(根目录右侧))
收益回报项目;
}

看起来非常合理的代码,但存在性能问题。假设这棵树很深。然后,在大多数情况下都会构建O(h)个嵌套迭代器。在外部迭代器上调用“MoveNext”将对MoveNext进行O(h)嵌套调用。因为它对一个有n个项目的树执行O(n)次,所以算法是O(hn)。由于二叉树的高度是lgn,我必须维护一堆代码,这些代码来自一个绝对痴迷于收益率和IEnumerable的家伙。问题是,我们使用的许多第三方API以及我们自己的许多代码都依赖于列表或数组。所以我最后不得不做:

IEnumerable<foo> myFoos = getSomeFoos();
List<foo> fooList = new List<foo>(myFoos);
thirdPartyApi.DoStuffWithArray(fooList.ToArray());
IEnumerable myFoos=getSomeFoos();
列表傻瓜=新列表(myFoos);
第三方yapi.dostufwitharray(愚人主义者ToArray());

不一定不好,但处理起来有点烦人,有时会导致在内存中创建重复列表以避免重构所有内容。

Eric Lippert提出了一个很好的观点(可惜C#没有)。我要补充的是,有时枚举过程由于其他原因而代价高昂,因此,如果您打算对IEnumerable进行多次迭代,则应该使用列表

例如,LINQ to对象是建立在“收益回报”基础上的。如果您编写了一个慢LINQ查询(例如,将一个大列表过滤成一个小列表,或者进行排序和分组),最好对查询结果调用
ToList()
,以避免枚举
foreach(T x in GetMyStuff()) {
    if (...)
        MyCollection.Add(...);
        // Oops, now GetMyStuff() will throw an exception
        // because MyCollection was modified.
}
IEnumerable<UserRight> GetSuperUserRights() {
    if(SuperUsersAllowed) {
        yield return UserRight.Add;
        yield return UserRight.Edit;
        yield return UserRight.Remove;
    }
}
IEnumerable<UserRight> GetSuperUserRights() {
    return SuperUsersAllowed
           ? new[] {UserRight.Add, UserRight.Edit, UserRight.Remove}
           : Enumerable.Empty<UserRight>();
}