2012-01-19 9 views
3

誰かが私がここで何が欠けているか説明してもらえますか?私の基本的な理解に基づいて、結果が使用されるときにlinq結果が計算され、次のコードでその結果を見ることができます。LINQ晩期バインディングの混乱

static void Main(string[] args) 
{ 
    Action<IEnumerable<int>> print = (x) => 
    { 
     foreach (int i in x) 
     { 
      Console.WriteLine(i); 
     } 
    }; 

    int[] arr = { 1, 2, 3, 4, 5 }; 
    int cutoff = 1; 
    IEnumerable<int> result = arr.Where(x => x < cutoff); 
    Console.WriteLine("First Print"); 
    cutoff = 3; 
    print(result); 
    Console.WriteLine("Second Print"); 
    cutoff = 4; 
    print(result); 
    Console.Read(); 
} 

出力:

 
First Print 
1 
2 
Second Print 
1 
2 
3 

は今、私は

IEnumerable<int> result = arr.Take(cutoff); 

arr.Where(x => x < cutoff); 

を変更し、出力は以下の通りです。

 
First Print 
1 
Second Print 
1 

なぜ、Takeでは変数の現在の値を使用しませんか?

+0

これは遅いバインディングとは関係ありません。 –

答えて

3

ここで混乱することがいくつかあります。

レイトバインディング:コードの意味は、コンパイル後に決定されます。例えば、x.DoStuff()は、xの型のオブジェクトがDoStuff()メソッド(拡張メソッドとデフォルトの引数も考慮して)を持っていることをコンパイラがチェックし、それが出力するコードでその呼び出しを生成するか、コンパイラで失敗した場合に、それ以外の場合はエラーです。実行時にDoStuff()メソッドの検索が実行され、DoStuff()メソッドがない場合は実行時例外がスローされます。それぞれの長所と短所があります.C#は通常早期バインドされていますが、遅延バインディング(最も簡単にはdynamicを介してサポートされていますが、反射を含むより複雑なアプローチもカウントされます)。

遅延実行厳密に言えば、すべてのLinqメソッドはただちに結果を生成します。しかし、その結果は、それ自体が列挙されているときに適切な方法で処理される、列挙可能なオブジェクトへの参照を格納するオブジェクト(前のLinqメソッドの結果であることが多い)です。例えば、我々は我々自身Takeメソッドを書くことができますように:

var taken4 = someEnumerable.Take(4);//taken4 has a value, so we've already done 
            //something. If it was going to throw 
            //an argument exception it would have done so 
            //by now. 

var firstTaken = taken4.First();//only now does the object in taken4 
             //do the further processing that iterates 
             //through someEnumerable. 

キャプチャ変数::私たちは、変数を利用する際、通常、私たちが利用することが、我々はそれを使用今

private static IEnumerable<T> TakeHelper<T>(IEnumerable<T> source, int number) 
{ 
    foreach(T item in source) 
    { 
    yield return item; 
    if(--number == 0) 
     yield break; 
    } 
} 
public static IEnumerable<T> Take<T>(this IEnumerable<T> source, int number) 
{ 
    if(source == null) 
    throw new ArgumentNullException(); 
    if(number < 0) 
    throw new ArgumentOutOfRangeException(); 
    if(number == 0) 
    return Enumerable.Empty<T>(); 
    return TakeHelper(source, number); 
} 

、それは、これが2とを印刷していることはかなり直感的です

int i = 2; 
string s = "abc"; 
Console.WriteLine(i); 
Console.WriteLine(s); 
i = 3; 
s = "xyz"; 

:どのように現在の状態のであり、3およびxyzではない。

int i = 2; 
string s = "abc"; 
Action λ =() => 
{ 
    Console.WriteLine(i); 
    Console.WriteLine(s); 
}; 
i = 3; 
s = "xyz"; 
λ(); 

作成を:我々は、変数として、それを「キャプチャ」されている変数を利用して、そして私たちは、それはデリゲートが呼び出されたときに持っている値を使用して終了しますけれども、匿名関数とラムダ式では、 λisの値を使用しますが、λが呼び出されたときにisをどうするかのような命令セットを作成しません。その場合にのみ、isの値が使用されます。

全部まとめて:いずれの場合でも、遅延バインディングはありません。それはあなたの質問に無関係です。

両方とも実行が遅延しています。 Takeへの呼び出しとWhereへの呼び出しの両方が列挙されるときにarrで動作する列挙可能なオブジェクトを返します。

キャプチャされた変数は1つだけです。 Takeへの呼び出しは、その値を利用する整数をTakeTakeに直接渡します。 Whereの呼び出しは、ラムダ式から作成されたFunc<int, bool>を渡し、ラムダ式はintという変数を取り込みます。 Whereはこのキャプチャについて何も知らないが、Funcはそうである。彼らはcutoffをどのように扱うかで二つはとても異なる挙動を示す理由です

+0

ありがとうございました。私は遅い拘束を知っていましたが、私は複数のことに取り組んでいました。遅れた実行ではなく間違った言葉を使っていました。しかし、あなたの答えを読むのは面白かったです。ありがとう。 – mchicago

+0

あなたの実装例では、 'Take'の実装では、あなたが指定したところで引数例外がスローされません。あなたが書いたコードは、最初のMoveNextまで実行されません。外部のメソッドでパラメータの検証を行い、yieldで記述されたヘルパーメソッドを呼び出す必要があります。 –

+0

@GideonEngelberthキャッチをありがとう。私はそれが実行されるのではなく読み込まれることを意図しているので簡潔にしたいと思っていましたが、むしろ書かれたように私のポイントを打ち負かしました。実際のことを詳しく見てみると、負の数をゼロとして扱うべきだと分かっていますが、私の議論を打ち負かすことはないので、そこにバグを残しておきます。 –

1

Takeは、元の変数を変更したときに変更することはできないので、ラムダではなく整数です。

6

あなたの見ている動作は、LINQ関数への引数が評価されるさまざまな方法から来ています。 Whereメソッドは、参照によって値cutoffをキャプチャするラムダを受信します。それは要求時に評価されるため、その時点での値はcutoffです。

メソッド(およびSkipのような同様のメソッド)は、intパラメータをとり、cutoffが渡されます。使用される値は、Takeメソッドが呼び出された瞬間のcutoffの値であり、クエリが評価されるときではありません。

注:ここでのレイトバインドという用語は少し誤りです。遅延バインディングは、一般に、式がバインドするメンバーが実行時とコンパイル時に決定されるプロセスを指します。 C#ではdynamicまたはリフレクションでこれを実現します。要求に応じて部品を評価するLINQの動作は、遅延実行として知られています。

+0

ありがとうございましたJaredPar。 – mchicago