在 C# 中,为什么匿名方法不能包含 yield 语句?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1217729/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
In C#, why can't an anonymous method contain a yield statement?
提问by Lance Fisher
I thought it would be nice to do something like this (with the lambda doing a yield return):
我认为做这样的事情会很好(使用 lambda 进行收益回报):
public IList<T> Find<T>(Expression<Func<T, bool>> expression) where T : class, new()
{
IList<T> list = GetList<T>();
var fun = expression.Compile();
var items = () => {
foreach (var item in list)
if (fun.Invoke(item))
yield return item; // This is not allowed by C#
}
return items.ToList();
}
However, I found out that I can't use yield in anonymous method. I'm wondering why. The yield docsjust say it is not allowed.
但是,我发现我不能在匿名方法中使用 yield。我想知道为什么。该产量的文档只是说,这是不允许的。
Since it wasn't allowed I just created List and added the items to it.
由于不允许,我只是创建了 List 并将项目添加到其中。
采纳答案by Thomas Levesque
Eric Lippert recently wrote a series of blog posts about why yield is not allowed in some cases.
Eric Lippert 最近写了一系列关于为什么在某些情况下不允许产量的博客文章。
EDIT2:
编辑2:
- Part 7(this one was posted later and specifically addresses this question)
- 第 7 部分(这个是稍后发布的,专门解决了这个问题)
You will probably find the answer there...
你可能会在那里找到答案......
EDIT1: this is explained in the comments of Part 5, in Eric's answer to Abhijeet Patel's comment:
EDIT1:这在第 5 部分的评论中进行了解释,在 Eric 对 Abhijeet Patel 评论的回答中:
Q :
问:
Eric,
Can you also provide some insight into why "yields" are not allowed inside an anonymous method or lambda expression
埃里克,
您能否还提供一些有关为什么匿名方法或 lambda 表达式中不允许使用“收益”的见解
A :
A :
Good question. I would love to have anonymous iterator blocks. It would be totally awesome to be able to build yourself a little sequence generator in-place that closed over local variables. The reason why not is straightforward: the benefits don't outweigh the costs. The awesomeness of making sequence generators in-place is actually pretty small in the grand scheme of things and nominal methods do the job well enough in most scenarios. So the benefits are not that compelling.
The costs are large. Iterator rewriting is the most complicated transformation in the compiler, and anonymous method rewriting is the second most complicated. Anonymous methods can be inside other anonymous methods, and anonymous methods can be inside iterator blocks. Therefore, what we do is first we rewrite all anonymous methods so that they become methods of a closure class. This is the second-last thing the compiler does before emitting IL for a method. Once that step is done, the iterator rewriter can assume that there are no anonymous methods in the iterator block; they've all be rewritten already. Therefore the iterator rewriter can just concentrate on rewriting the iterator, without worrying that there might be an unrealized anonymous method in there.
Also, iterator blocks never "nest", unlike anonymous methods. The iterator rewriter can assume that all iterator blocks are "top level".
If anonymous methods are allowed to contain iterator blocks, then both those assumptions go out the window. You can have an iterator block that contains an anonymous method that contains an anonymous method that contains an iterator block that contains an anonymous method, and... yuck. Now we have to write a rewriting pass that can handle nested iterator blocks and nested anonymous methods at the same time, merging our two most complicated algorithms into one far more complicated algorithm. It would be really hard to design, implement, and test. We are smart enough to do so, I'm sure. We've got a smart team here. But we don't want to take on that large burden for a "nice to have but not necessary" feature. -- Eric
好问题。我希望有匿名迭代器块。能够就地为自己构建一个小序列生成器来关闭局部变量,这将是非常棒的。原因很简单:收益不会超过成本。就地制作序列生成器的好处实际上在宏观计划中非常小,而名义方法在大多数情况下都可以很好地完成这项工作。所以好处并不那么引人注目。
成本很大。迭代器重写是编译器中最复杂的转换,匿名方法重写是第二复杂的。匿名方法可以在其他匿名方法中,匿名方法可以在迭代器块中。因此,我们首先要做的是重写所有匿名方法,使它们成为闭包类的方法。这是编译器在为方法发出 IL 之前所做的第二件事。一旦这一步完成,迭代器重写器就可以假设迭代器块中没有匿名方法;它们都已经被重写了。因此迭代器重写器可以专注于重写迭代器,而不必担心其中可能存在未实现的匿名方法。
此外,与匿名方法不同,迭代器块永远不会“嵌套”。迭代器重写器可以假设所有迭代器块都是“顶级”的。
如果允许匿名方法包含迭代器块,那么这两个假设都不成立。您可以拥有一个包含匿名方法的迭代器块,该匿名方法包含一个包含匿名方法的迭代器块,并且...哎呀。现在我们必须编写一个可以同时处理嵌套迭代器块和嵌套匿名方法的重写过程,将我们两个最复杂的算法合并为一个更复杂的算法。设计、实现和测试真的很难。我们足够聪明,可以这样做,我敢肯定。我们这里有一个聪明的团队。但是我们不想为“很好但不是必需的”功能承担那么大的负担。——埃里克
回答by Lasse V. Karlsen
Unfortunately I don't know why they didn't allow this, since of course it's entirely possible to do envision how this would work.
不幸的是,我不知道他们为什么不允许这样做,因为当然完全有可能想象这将如何工作。
However, anonymous methods are already a piece of "compiler magic" in the sense that the method will be extracted either to a method in the existing class, or even to a whole new class, depending on whether it deals with local variables or not.
然而,匿名方法已经是一种“编译器魔术”,因为该方法将被提取到现有类中的方法,甚至是一个全新的类,具体取决于它是否处理局部变量。
Additionally, iterator methods using yield
is also implemented using compiler magic.
此外,迭代器方法 usingyield
也是使用编译器魔法实现的。
My guess is that one of these two makes the code un-identifiable to the other piece of magic, and that it was decided to not spend time on making this work for the current versions of the C# compiler. Of course, it might not be a concious choice at all, and that it just doesn't work because nobody thought to implement it.
我的猜测是,这两个中的一个使代码无法被另一个魔法识别,并且决定不花时间为当前版本的 C# 编译器进行这项工作。当然,这可能根本就不是一个有意识的选择,它只是行不通,因为没有人想过要实施它。
For a 100% accurate question I would suggest you use the Microsoft Connectsite and report a question, I'm sure you'll get something usable in return.
对于 100% 准确的问题,我建议您使用Microsoft Connect站点并报告问题,我相信您会得到一些有用的回报。
回答by ShuggyCoUk
Eric Lippert has written an excellent series of articles on the limitations (and design decisions influencing those choices) on iterator blocks
Eric Lippert 撰写了一系列关于迭代器块的限制(以及影响这些选择的设计决策)的优秀文章
In particular iterator blocks are implemented by some sophisticated compiler code transformations. These transformations would impact with the transformations which happen inside anonymous functions or lambdas such that in certain circumstances they would both try to 'convert' the code into some other construct which was incompatible with the other.
特别是迭代器块是通过一些复杂的编译器代码转换来实现的。这些转换会影响匿名函数或 lambda 表达式中发生的转换,因此在某些情况下,它们都会尝试将代码“转换”为与另一个不兼容的其他结构。
As a result they are forbidden from interaction.
因此,他们被禁止互动。
How iterator blocks work under the hood is dealt with well here.
这里很好地处理了迭代器块如何在幕后工作。
As a simple example of an incompatibility:
作为不兼容的一个简单示例:
public IList<T> GreaterThan<T>(T t)
{
IList<T> list = GetList<T>();
var items = () => {
foreach (var item in list)
if (fun.Invoke(item))
yield return item; // This is not allowed by C#
}
return items.ToList();
}
The compiler is simultaneously wanting to convert this to something like:
编译器同时希望将其转换为以下内容:
// inner class
private class Magic
{
private T t;
private IList<T> list;
private Magic(List<T> list, T t) { this.list = list; this.t = t;}
public IEnumerable<T> DoIt()
{
var items = () => {
foreach (var item in list)
if (fun.Invoke(item))
yield return item;
}
}
}
public IList<T> GreaterThan<T>(T t)
{
var magic = new Magic(GetList<T>(), t)
var items = magic.DoIt();
return items.ToList();
}
and at the same time the iterator aspect is trying to do it's work to make a little state machine. Certain simple examples might work with a fair amount of sanity checking (first dealing with the (possibly arbitrarily) nested closures) then seeing if the very bottom level resulting classes could be transformed into iterator state machines.
与此同时,迭代器方面正试图做一个小状态机的工作。某些简单的示例可能需要进行大量的健全性检查(首先处理(可能是任意的)嵌套闭包),然后查看是否可以将最底层的结果类转换为迭代器状态机。
However this would be
然而,这将是
- Quite a lot of work.
- Couldn't possibly work in all cases without at the very least the iterator block aspect being able to prevent the closure aspect from applying certain transformations for efficiency (like promoting local variables to instance variables rather than a fully fledged closure class).
- If there was even a slight chance of overlap where it was impossible or sufficiently hard to not be implemented then the number of support issues resulting would likely be high since the subtle breaking change would be lost on many users.
- It can be very easily worked around.
- 相当多的工作。
- 不可能在所有情况下都工作,至少迭代器块方面能够阻止闭包方面应用某些转换以提高效率(例如将局部变量提升为实例变量,而不是完全成熟的闭包类)。
- 如果在不可能或难以实施的情况下甚至有轻微的重叠机会,那么由此产生的支持问题的数量可能会很高,因为许多用户会丢失细微的重大更改。
- 它可以很容易地解决。
In your example like so:
在你的例子中,像这样:
public IList<T> Find<T>(Expression<Func<T, bool>> expression)
where T : class, new()
{
return FindInner(expression).ToList();
}
private IEnumerable<T> FindInner<T>(Expression<Func<T, bool>> expression)
where T : class, new()
{
IList<T> list = GetList<T>();
var fun = expression.Compile();
foreach (var item in list)
if (fun.Invoke(item))
yield return item;
}
回答by Thomas Levesque
I would do this:
我会这样做:
IList<T> list = GetList<T>();
var fun = expression.Compile();
return list.Where(item => fun.Invoke(item)).ToList();
Of course you need the System.Core.dll referenced from .NET 3.5 for the Linq method. And include:
当然,Linq 方法需要从 .NET 3.5 引用的 System.Core.dll。并包括:
using System.Linq;
Cheers,
干杯,
Sly
狡猾
回答by Bolpat
Maybe its just a syntax limitation. In Visual Basic .NET, which is very similar to C#, it is perfectly possible while awkward to write
也许它只是一个语法限制。在与 C# 非常相似的 Visual Basic .NET 中,虽然编写起来很笨拙,但完全有可能
Sub Main()
Console.Write("x: ")
Dim x = CInt(Console.ReadLine())
For Each elem In Iterator Function()
Dim i = x
Do
Yield i
i += 1
x -= 1
Loop Until i = x + 20
End Function()
Console.WriteLine($"{elem} to {x}")
Next
Console.ReadKey()
End Sub
Also note the parentheses ' here
; the lambda function Iterator Function
...End Function
returnsan IEnumerable(Of Integer)
but is notsuch an object itself. It must be called to get that object.
还要注意括号' here
;lambda 函数Iterator Function
......End Function
返回一个IEnumerable(Of Integer)
但不是这样的对象本身。必须调用它来获取该对象。
The converted code by [1] raises errors in C# 7.3 (CS0149):
[1] 转换后的代码在 C# 7.3 (CS0149) 中引发错误:
static void Main()
{
Console.Write("x: ");
var x = System.Convert.ToInt32(Console.ReadLine());
// ERROR: CS0149 - Method name expected
foreach (var elem in () =>
{
var i = x;
do
{
yield return i;
i += 1;
x -= 1;
}
while (!i == x + 20);
}())
Console.WriteLine($"{elem} to {x}");
Console.ReadKey();
}
I strongly disagree to the reason given in the other answers that it's difficult for the compiler to handle. The Iterator Function()
you see in the VB.NET example is specifically created for lambda iterators.
我强烈不同意其他答案中给出的编译器难以处理的原因。在Iterator Function()
你的VB.NET例子中看到的是专门为拉姆达创建的迭代器。
In VB, there is the Iterator
keyword; it has no C# counterpart. IMHO, there is no real reason this is not a feature of C#.
在VB中,有Iterator
关键字;它没有 C# 对应物。恕我直言,没有真正的理由这不是 C# 的功能。
So if you really, really want anonymous iterator functions, currently use Visual Basic or (I haven't checked it) F#, as stated in a comment of Part #7in @Thomas Levesque's answer (do Ctrl+F for F#).
因此,如果您真的非常想要匿名迭代器函数,请当前使用 Visual Basic 或(我还没有检查过)F#,如@Thomas Levesque 的答案中第 7 部分的评论中所述(对 F# 执行 Ctrl+F)。