C# 在C语言中,为什么匿名方法不能包含yield语句?

C# 在C语言中,为什么匿名方法不能包含yield语句?,c#,yield,anonymous-methods,yield-return,C#,Yield,Anonymous Methods,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 = () => {

我认为用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();
}
然而,我发现我不能在匿名方法中使用收益率。我想知道为什么。他们只是说这是不允许的


因为这是不允许的,所以我只是创建了一个列表并将项目添加到其中

不幸的是,我不知道他们为什么不允许这样做,因为当然,完全可以设想这将如何工作

然而,匿名方法已经是一种编译器魔法,因为该方法将被提取到现有类中的一个方法,甚至是一个全新的类,这取决于它是否处理局部变量

此外,使用yield的迭代器方法也使用编译器magic实现

我的猜测是,这两种方法中的一种会使代码无法被另一种方法识别,因此决定不花时间在当前版本的C编译器上。当然,这可能根本不是一个明智的选择,而且它根本不起作用,因为没有人想到要实现它


对于一个100%准确的问题,我建议你使用该网站并报告一个问题,我相信你会得到一些有用的东西作为回报。

埃里克·利珀特(Eric Lippert)写了一系列优秀的文章,介绍了影响这些选择的限制和设计决策

特别是迭代器块是通过一些复杂的编译器代码转换实现的。这些转换会影响匿名函数或lambda内部发生的转换,因此在某些情况下,它们都会尝试将代码“转换”为与其他构造不兼容的其他构造

因此,他们被禁止互动

迭代器块如何在引擎盖下工作得到了很好的处理

作为不兼容的一个简单示例:

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();
}
编译器同时希望将其转换为以下内容:

// 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();
}
同时,迭代器方面正在努力制作一个小型状态机。某些简单的示例可能需要进行大量的健全性检查,首先处理可能任意嵌套的闭包,然后查看最底层的结果类是否可以转换为迭代器状态机

然而,这将是

相当多的工作。 如果迭代器块方面至少能够阻止闭包方面应用某些转换以提高效率,比如将局部变量提升到实例变量,而不是完全成熟的闭包类,那么就不可能在所有情况下都工作。 如果在不可能或很难实现的情况下,甚至有轻微的重叠机会,那么所产生的支持问题的数量可能会很高,因为细微的突破性更改将在许多用户身上丢失。 它可以很容易地处理。 在你这样的例子中:

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

Eric Lippert最近写了一系列关于为什么在某些情况下不允许收益率的博客文章

编辑2:

这篇文章后来发表,专门针对这个问题 你可能会在那里找到答案

编辑1:这在第5部分的评论中得到了解释,在Eric对Abhijeet Patel评论的回答中:

问:

埃里克

您还可以提供一些关于 为什么收益率不被允许在 匿名方法或lambda表达式

A:

好问题。我很想 匿名迭代器块。是的 非常棒,能够建造 你自己一个小序列发生器 在当地关闭的地方 变量。原因是 直截了当:好处并不重要 超过成本。令人敬畏的 使序列生成器就位是非常重要的 实际上在大城市里是相当小的 事物的图式与命名方法 在大多数情况下,这项工作做得足够好 情节。因此,好处并不明显 这很有说服力

成本很高。迭代器 重写是最复杂的 编译器中的转换,以及 匿名方法重写是 其次是最复杂的。匿名的 方法可以位于其他匿名方法中 方法,并且可以使用匿名方法 在迭代器块内部。因此 我们要做的是首先重写所有 匿名方法,使它们成为 闭包类的方法。这是 最后一件事是编译器 在发出方法的IL之前执行。 完成该步骤后,迭代器 重写器可以假定没有 迭代器中的匿名方法 块它们都被改写了 已经因此,迭代器 重写器只需专注于 重写迭代器,而不使用 担心可能会有 其中未实现的匿名方法

此外,迭代器块从不嵌套, 与匿名方法不同。迭代器 重写器可以假设所有迭代器 积木是顶层的

如果允许匿名方法 包含iter 一个街区,然后两个都是 这些假设都被抛到了脑后。 可以有一个迭代器块 包含一个匿名方法,该方法 包含一个匿名方法,该方法 包含一个迭代器块 包含匿名方法,并且。。。 讨厌。现在我们必须写一篇重写 可以处理嵌套迭代器的过程 块和嵌套的匿名方法 同时,将我们两个最重要的 复杂的算法集于一身 更复杂的算法。会的 真的很难设计,实施, 和测试。我们足够聪明去做 所以,我敢肯定。我们有一个聪明的团队 在这里但我们不想承担责任 对于一个好孩子来说,这是一个沉重的负担 但不是必需的特征埃里克

我会这样做:

IList<T> list = GetList<T>();
var fun = expression.Compile();

return list.Where(item => fun.Invoke(item)).ToList();
干杯


Sly可能只是一个语法限制。在与C非常相似的VisualBasic.NET中,这是完全可能的,尽管编写起来很麻烦

副总管 控制台.Writex: Dim x=CIntConsole.ReadLine 对于迭代器函数中的每个元素 尺寸i=x 做 产量一 i+=1 x-=1 循环直到i=x+20 端函数 Console.WriteLine${elem}到{x} 下一个 Console.ReadKey 端接头 请注意此处的括号';lambda函数迭代器函数…End函数返回一个IEnumerableOf整数,但它本身不是这样的对象。必须调用它才能获取该对象

[1]转换的代码在C 7.3 CS0149中引发错误:

静态空隙总管 { 控制台。Writex:; var x=System.Convert.ToInt32Console.ReadLine; //错误:CS0149-应为方法名称 foreach var elem in=> { var i=x; 做 { 收益率i; i+=1; x-=1; } 而!i==x+20; } Console.WriteLine${elem}到{x}; Console.ReadKey; } 我强烈反对其他答案中给出的编译器难以处理的原因。您在VB.NET示例中看到的迭代器函数是专门为lambda迭代器创建的

在VB中,有迭代器关键字;它没有对应的C。嗯,没有真正的理由这不是C的一个特性


因此,如果您真的非常想要匿名迭代器函数,当前使用Visual Basic,或者我没有检查它F,正如在@Thomas Levesque的答案do Ctrl+F for F中的注释所述。

没有明确的原因说明编译器在取消所有闭包后不能执行通常的迭代器转换。你知道一个实际会带来一些困难的案例吗?顺便说一句,您的Magic类应该是Magic的。现在我们可以在C 5.0中使用匿名异步lambda,允许wait inside,我很想知道为什么他们还没有实现内部具有yield的匿名迭代器。或多或少,它是同一个状态机生成器。很有趣,特别是因为现在有了局部函数。我想知道这个答案是否过时了,因为它将在局部函数中获得收益。@Joshua但局部函数与匿名方法不同。。。在匿名方法中仍然不允许使用yield-return。
using System.Linq;