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>();
}