91

私はこのようなことをするのがいいと思いました(ラムダがyield returnを実行します):

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を使用できないことがわかりました。なぜだろうか。歩留まりのドキュメントには、許可されていないと書かれています。

許可されていなかったので、リストを作成してアイテムを追加しました。

4

5 に答える 5

116

Eric Lippertは最近、歩留まりが許可されない場合がある理由について一連のブログ投稿を書きました。

EDIT2:

  • パート7 (これは後で投稿され、特にこの質問に対処します)

あなたはおそらくそこに答えを見つけるでしょう...


編集1:これは、パート5のコメントで、AbhijeetPatelのコメントに対するEricの回答で説明されています。

Q:

エリック、

また、匿名メソッドまたはラムダ式内で「yields」が許可されない理由についての洞察を提供できますか?

A:

良い質問。匿名のイテレータブロックが欲しいです。ローカル変数を閉じる小さなシーケンスジェネレーターをインプレースで構築できるのは、まったく素晴らしいことです。そうしない理由は簡単です。メリットがコストを上回らないからです。シーケンスジェネレーターをインプレースで作成することの素晴らしさは、実際には物事の壮大なスキームではかなり小さく、名目上の方法はほとんどのシナリオで十分に機能します。したがって、メリットはそれほど魅力的ではありません。

コストが高いです。イテレータの書き換えはコンパイラで最も複雑な変換であり、匿名メソッドの書き換えは2番目に複雑です。匿名メソッドは他の匿名メソッド内に含めることができ、匿名メソッドはイテレーターブロック内に含めることができます。したがって、最初にすべての匿名メソッドを書き直して、クロージャクラスのメソッドになるようにします。これは、コンパイラがメソッドのILを発行する前に行う最後から2番目のことです。そのステップが完了すると、イテレーター・リライターは、イテレーター・ブロックに匿名メソッドがないと想定できます。それらはすべてすでに書き直されています。したがって、イテレータの書き換えは、未実現の匿名メソッドが存在する可能性があることを心配することなく、イテレータの書き換えに集中できます。

また、イテレータブロックは、匿名メソッドとは異なり、「ネスト」することはありません。イテレータリライタは、すべてのイテレータブロックが「トップレベル」であると想定できます。

匿名メソッドにイテレータブロックを含めることが許可されている場合、これらの仮定は両方ともウィンドウの外に出ます。匿名メソッドを含むイテレータブロックを含むイテレータブロックを持つことができます。匿名メソッドを含むイテレータブロックを含む匿名メソッドを含む匿名メソッドを含みます。次に、ネストされたイテレータブロックとネストされた匿名メソッドを同時に処理できる書き換えパスを作成し、2つの最も複雑なアルゴリズムを1つのはるかに複雑なアルゴリズムにマージする必要があります。設計、実装、およびテストは非常に困難です。私たちはそうするのに十分賢いです、私は確信しています。ここには賢いチームがいます。しかし、「持っていて必要ではない」機能のために、それほど大きな負担をかけたくはありません。-エリック

于 2009-08-01T23:21:38.253 に答える
22

Eric Lippertは、イテレータブロックの制限(およびそれらの選択に影響を与える設計上の決定)に関する優れた一連の記事を書いています。

特に、イテレータブロックは、いくつかの高度なコンパイラコード変換によって実装されます。これらの変換は、無名関数またはラムダ内で発生する変換に影響を与え、特定の状況では、両方がコードを他の構成と互換性のない他の構成に「変換」しようとします。

結果として、彼らは相互作用を禁じられています。

イテレータブロックが内部でどのように機能するかは、ここでうまく処理されます。

非互換性の簡単な例として:

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. 少なくともイテレータブロックアスペクトが、クロージャアスペクトが効率のために特定の変換を適用するのを防ぐことができなければ、すべての場合に機能することはできません(本格的なクロージャクラスではなく、ローカル変数をインスタンス変数に昇格させるなど)。
    • 実装が不可能または十分に困難な重複の可能性がわずかでもあった場合、多くのユーザーで微妙な重大な変更が失われるため、結果として生じるサポートの問題の数は多くなる可能性があります。
  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;
}
于 2009-08-01T23:18:29.633 に答える
5

残念ながら、なぜ彼らがこれを許可しなかったのかはわかりません。もちろん、これがどのように機能するかを想像することは完全に可能だからです。

ただし、匿名メソッドは、ローカル変数を処理するかどうかに応じて、メソッドが既存のクラスのメソッドに抽出されるか、まったく新しいクラスに抽出されるという意味で、すでに「コンパイラの魔法」の一部です。

さらに、を使用するイテレータメソッドyieldもコンパイラマジックを使用して実装されます。

私の推測では、これら2つのうちの1つは、他の魔法のコードを識別できないようにし、C#コンパイラの現在のバージョンでこの作業を行うことに時間を費やさないことに決めました。もちろん、それはまったく意識的な選択ではないかもしれません、そして誰もそれを実装しようとは思わなかったので、それは単に機能しません。

100%正確な質問については、Microsoft Connectサイトを使用して質問を報告することをお勧めします。その見返りとして、何か使えるものが手に入ると確信しています。

于 2009-08-01T23:17:15.610 に答える
1

私はこれを行います:

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

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

もちろん、Linqメソッドには.NET3.5から参照されるSystem.Core.dllが必要です。そして含める:

using System.Linq;

乾杯、

スライ

于 2009-09-19T20:42:58.333 に答える
0

多分それは単なる構文上の制限です。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

括弧にも注意してください' here; ラムダ関数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#の機能ではない本当の理由はありません。

したがって、本当に匿名のイテレータ関数が必要な場合は、@ Thomas Levesqueの回答のパート#7のコメントに記載されているように、現在Visual Basicまたは(まだチェックしていません)F#を使用してください(F#の場合はCtrl + Fを実行してください)。

于 2018-10-08T15:20:33.150 に答える