yield 和 List.AsEnumerable 的区别
yield 和 List.AsEnumerable 的区别
直到现在,我发现理解yield很困难。但现在我正在掌握它。现在,在一个项目中,如果我返回List,Microsoft代码分析会发出警告。因此,通常我会执行所有必要的逻辑部分,并将列表作为IEnumerable返回。我想知道两者之间的区别。也就是说,如果我正在使用yield return还是其他方式。
这里是一个非常简单的例子,我展示了它,通常情况下代码会稍微复杂一些。
private static IEnumerable getIntFromList(List inputList) { var outputlist = new List(); foreach (var i in inputList) { if (i %2 ==0) { outputlist.Add(i); } } return outputlist.AsEnumerable(); } private static IEnumerable getIntFromYeild(List inputList) { foreach (var i in inputList) { if (i%2 == 0) { yield return i; } } }
我能看到的一个显着的好处是代码行数较少。但还有其他好处吗?我应该改变并更新我的函数,让它们返回yield而不是List吗?做事情的最好方法或更好的方法是什么?
在这里,我可以在List上使用简单的lambda表达式,但通常情况下并非如此,这个例子是为了理解最佳编码方法而特别设计的。
yield return
的关键点是它是 不缓存 的;迭代块是一个状态机,当数据被迭代时恢复执行。这使得它非常适用于非常大的数据源(甚至是无限列表),因为可以避免拥有巨大的内存列表。
以下是一个完全定义良好的迭代器块,可以成功地进行迭代:
Random rand = new Random(); while(true) yield return rand.Next();
我们可以执行以下操作:
for(int i in TheAbove().Take(20)) Console.WriteLine(i);
虽然显然,任何迭代到末尾的东西(例如 Count()
等)都将无限运行而不结束 - 这不是一个好主意。
在您的示例中,代码可能过于复杂。 List
版本可以是:
return new List(inputList);
取决于您想要做什么,yield return
可以很简单,比如:
foreach(var item in inputList) yield return item;
虽然显然这仍在查看源数据:对 inputList
的更改可能会破坏迭代器。 如果您认为“这很好”,那么坦率地说,您可以只需:
return inputList;
如果这不好,在这种情况下迭代器块有点过度,可以使用:
return new List(inputList);
足矣。
为了完整起见:AsEnumerable
只返回原始源,类型转换;这是:
return inputList;
版本。 这有一个重要的考虑因素,即它不保护您的列表,如果这是一个问题。 因此,如果您正在考虑:
return someList.AsEnumerable(); // so they can only iterate it, not Add
那么将 不会 生效;恶意调用者仍然可以:
var list = (IList) theAbove; int mwahaahahaha = 42; list.Add(mwahaahahaha);
你的第一个例子仍然会急切地完成所有工作并在内存中创建一个列表。事实上,调用 AsEnumerable()
是毫无意义的 - 你可以直接使用:
return outputlist;
你的第二个例子是惰性的 - 它只会在客户端拉取数据时做出必要的工作。
展示区别最简单的方法可能是在 if (i % 2 ==0)
语句中添加一个 Console.WriteLine
调用:
Console.WriteLine("Got a value to return: " + i);
如果在客户端代码中添加一个 Console.WriteLine
调用:
foreach (int value in getIntFromList(list)) { Console.WriteLine("Received value: " + value); }
...你会发现,在第一个代码中,你先看到所有的“得到一个值”的行,然后是所有的“接收到的值”行。对于迭代块,你会看到它们交替出现。
现在想象一下,你的代码实际上正在执行一些昂贵的操作,且你的列表非常长,客户端只想要前三个值...使用第一个代码,你会做许多无关的工作。使用惰性方法,你只做你需要的工作,以“即时”的方式。第二种方法也不需要将所有结果缓存到内存中 - 再次提醒,如果输入列表非常大,即使你只想一次使用一个值,你也会得到一个很大的输出列表。