-->

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

2019-05-12 22:56发布

我认为这将是很好做这样的事情(与拉姆达做产返程):

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

然而,我发现我不能使用匿名方法的产量。 我不知道为什么。 该产量的文档只是说,这是不允许的。

因为它不允许我刚刚创建列表和添加的项目给它。

Answer 1:

埃里克利珀最近写了一系列关于为什么产量不某些情况下允许的博客文章。

  • 第1部分
  • 第2部分
  • 第3部分
  • 第4部分
  • 第5部分
  • 第6部分

EDIT2:

  • 第7部分 (这个后来被张贴和专门解决这个问题)

你可能会找到答案,那么...


EDIT1:这是在第5部分的注释说明,在Eric的回答Abhijeet帕特尔的评论:

问:

埃里克,

你也可以提供一些洞察为什么“产生”不允许匿名方法或lambda表达式内

A :

好问题。 我很想有匿名的迭代块。 这将是完全真棒,以便能够建立自己就地有点序列发生器关闭了局部变量。 之所以不是很简单:好处不超过成本。 就地制作序列发生器的迷死实际上是非常小的事情,并标称方法的宏伟计划做的工作在大多数情况下不够好。 因此,好处是没有那么引人注目。

代价是巨大的。 迭代器重写是在编译器中最复杂的转变,匿名方法重写是第二个最复杂的。 匿名方法可以是内部的其他匿名方法,并且匿名方法可以在里面迭代块。 因此,我们要做的就是首先我们重写所有匿名方法,使他们成为一个封闭类的方法。 这是第二最后一件事发光IL一种方法之前的编译器。 一旦步骤完成后,迭代器重写可以假设有在迭代器块没有匿名方法; 他们都已经被改写。 因此,迭代器重写可以只专注于重写迭代器,而不用担心有可能是一个未实现的匿名方法在那里。

此外,迭代器块从不“鸟巢”,不像匿名方法。 迭代器重写可以假定所有迭代器块是“顶级”。

如果匿名方法允许包含迭代块,那么这两个假设,走出去的窗口。 你可以有一个包含包含包含包含匿名方法和迭代器块匿名方法匿名方法的迭代器块......呸。 现在我们就来写一个重写通行证,可以在同一时间处理嵌套迭代器块和嵌套匿名方法,将我们的两个最复杂的算法到一个更为复杂的算法。 这将是真的很难设计,实施和测试。 我们有足够的智慧的话,我敢肯定。 我们有一个聪明的团队在这里。 但是,我们不想承担这种负担大了“不错的,但没有必要”的特征。 - 埃里克



Answer 2:

埃里克利珀写了一本优秀的系列文章上(影响这些选择和设计决策)的限制迭代器块

特别是迭代器块是由一些复杂的编译器代码的转换来实现。 这些转变将与匿名函数或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();
}

而在同一时间的迭代器方面试图做到这一点的工作,使一些状态机。 某些简单的例子可能具备相当健全检查(先处理(可能任意)嵌套闭包),然后看是否产生类最底部水平有可能转化为迭代器状态机的工作。

然而,这将是

  1. 相当多的工作。
  2. 不可能工作在所有情况下没有最起码的迭代器块方面能够防止封盖方面的应用某些转换效率(如促进局部变量实例变量,而不是一个完全成熟的closure类)。
    • 如果有重叠,即使是轻微的机会,它是不可能的或者足够很难不被实现,那么导致的将可能很高,因为细微重大更改将在许多用户失去支持问题的数量。
  3. 它可以被周围很容易的工作。

在你的榜样,像这样:

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


Answer 3:

不幸的是,我不知道为什么他们没有允许这样做,因为这当然是完全有可能做到预想如何做到这一点的工作。

然而,匿名方法已经在这个意义上的一块“编译器魔法”,该方法将被提取要么方法在现有的班级,甚至到了一个新的类,取决于它是否与局部变量或没有交易。

此外,使用迭代方法yield使用编译器魔法也可以实现。

我的猜测是,这两个中的一个使得代码未识别的其他片神奇的,而且它决定不花时间做这个工作,为C#编译器的最新版本。 当然,这可能不是一个Concious酒店选择的话,那它是行不通的,因为没有人会想到来实现它。

对于100%准确的问题,我会建议你使用微软连接网站和报告问题,我敢肯定你会得到有用的东西作为回报。



Answer 4:

我这样做:

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

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

当然,你需要从.NET 3.5中引用的LINQ的方法System.Core.dll。 其中包括:

using System.Linq;

干杯,

狡猾



Answer 5:

也许它只是一个语法限制。 在Visual Basic .NET中,这是非常类似于C#,这是完全可能的,而尴尬的写

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

还要注意括号' here ; lambda函数Iterator Function ... End Function 返回一个IEnumerable(Of Integer) ,但不是这样的对象本身。 它必须被调用,以获取该对象。

由转换后的代码[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();
}

我坚决不同意在其他的答案中给出的原因,它很难编译器来处理。 该Iterator Function()您在VB.NET例子中看到的是专门为拉姆达创建的迭代器。

在VB,有Iterator关键字; 它没有C#对应。 恕我直言,有没有真正的理由,这不是C#的一个特点。

所以,如果你真的,真的想匿名迭代器的功能,目前使用Visual Basic或(我没有检查它)F#,作为一个注释说明零件号7在@Thomas莱维斯克的答案(做按Ctrl + F为F#)。



文章来源: In C#, why can't an anonymous method contain a yield statement?