2

JonSkeetのReimplemntingLinqtoObjectsシリーズを実行していました。whereの記事の実装で、次のスニペットを見つけましたが、元のメソッドを2つに分割することで得られる利点がわかりません。

元の方法:

// Naive validation - broken! 
public static IEnumerable<TSource> Where<TSource>( 
    this IEnumerable<TSource> source, 
    Func<TSource, bool> predicate) 
{ 
    if (source == null) 
    { 
        throw new ArgumentNullException("source"); 
    } 
    if (predicate == null) 
    { 
        throw new ArgumentNullException("predicate"); 
    } 
    foreach (TSource item in source) 
    { 
        if (predicate(item)) 
        { 
            yield return item; 
        } 
    } 
}

リファクタリングされた方法:

public static IEnumerable<TSource> Where<TSource>( 
    this IEnumerable<TSource> source, 
    Func<TSource, bool> predicate) 
{ 
    if (source == null) 
    { 
        throw new ArgumentNullException("source"); 
    } 
    if (predicate == null) 
    { 
        throw new ArgumentNullException("predicate"); 
    } 
    return WhereImpl(source, predicate); 
} 

private static IEnumerable<TSource> WhereImpl<TSource>( 
    this IEnumerable<TSource> source, 
    Func<TSource, bool> predicate) 
{ 
    foreach (TSource item in source) 
    { 
        if (predicate(item)) 
        { 
            yield return item; 
        } 
    } 
} 

ジョンは言います-それは熱心な検証のためであり、それから残りの部分のために延期します。でも、わかりません。

誰かがそれをもう少し詳しく説明してもらえますか、これら2つの機能の違いは何ですか、そしてなぜ検証が一方で実行され、もう一方では熱心に実行されないのですか?

結論/解決策:

どの関数がイテレータージェネレーターであると判断されるかについての理解が不足していたため、混乱しました。IEnumerable<T>のようなメソッドのシグネチャに基づいていると思い ました。しかし、答えに基づいて、今私はそれを理解しました、それがyield ステートメントを使用する場合、メソッドはイテレーター-ジェネレーターです。

4

2 に答える 2

5

壊れたコードは単一のメソッドであり、実際には反復子ジェネレーターです。つまり、最初は何もせずにステート マシンを返すだけです。呼び出し元のコードがMoveNextを呼び出した場合(おそらく for-each ループの一部として) のみ、最初から最初の yield-return まですべてが実行されます。

正しいコードでWhereは、イテレータジェネレータではありません。つまり、通常どおり、すべてがすぐに実行されます。のみWhereImplです。そのため、検証はすぐに実行されますがWhereImpl、最初の yield リターンまでのコードは延期されます。

したがって、次のようなものがある場合:

IEnumerable<int> evens = list.Where(null); // Correct code gives error here.
foreach(int i in evens) // Broken code gives it here.

壊れたバージョンでは、反復を開始するまでエラーは発生しません。

于 2011-01-12T23:15:40.250 に答える
2

Jon は彼の記事でかなりうまく説明していると思いますが、その説明は、yieldステートメントがあるときにコンパイラがどのようにコードを生成するかを理解しているかどうかにかかっています。基本的に何が起こるかというと、反復からの項目の 1 つが必要になるまで呼び出されない (遅延実行) 反復子をコンパイラが生成することです。初期メソッドには、引数をチェックするコードと反復コードの両方が含まれています。コンパイラは、これらすべてをイテレータにまとめます。イテレータは、最初の項目が必要になるまで呼び出されません。これは、列挙可能な項目のいずれかにアクセスしようとするまで、検証が行われないことを意味します。

検証を含むメソッドと反復子ブロックを含むメソッドの 2 つのメソッドに分けることで、反復子の実行時ではなく、反復子の構築時に検証コードが確実に実行されるようにします。これは、反復子にバンドルされている唯一のコードが 2 番目のメソッドのコードであるためです。実行が延期される唯一のコードです。検証コードは、反復子の作成時に実行されます。

于 2011-01-12T23:26:16.487 に答える