質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.33%

  • C#

    7710questions

    C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

C# Linqとyieldが絡んだ時の処理順がわかりません

解決済

回答 4

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 1,298

tomotaka106

score 8

なぜ、この出力順になるのかわかりません。

C#でジェネリックメソッドを学習中です。
ネット上の情報を参考にしているのですが、私のレベルでは理解できない動きとなりました。
なぜ、この出力順になるのか、解説いただきたいです。

該当のソースコード

using System;
using System.Linq;
using System.Collections.Generic;

public static class Test
{
    public static void Main()
    {
        var list = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
        var query = list.Where(x => x % 2 == 0).Trace().Select(x => x * 10);
        foreach (var item in query) {
            Console.WriteLine(item);
        }
    }

    public static IEnumerable<T> Trace<T>(this IEnumerable<T> source)
    {
        foreach (var item in source) {
            Console.WriteLine(item);
            yield return item;
        }
    }
}

出力結果

2
20
4
40
6
60
8
80

私の予想では、

2
4
6
8
20
40
60
80


でした。
なぜ、最後のforeachが、途中で割り込むような出力になるのでしょうか?

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 4

+2

その答えは「遅延評価」ですね。
つまり、Whereに続く部分が実際に実行されるのは foreach が実行されるタイミングまで遅延されます。
そして、foreach から参照されそうになると、その時点で実行されるようになります。

この遅延評価の利点として、無限に続くシーケンスに対しても問題なく実行ができる点です。

例えば、整数すべて(つまり無限の値)から偶数を抜き出す。ただし100以下の場合に。

というような操作を望んだとします。
これくらいシンプルなら最初から100までの数字を準備してループすればいいだけですが、この部分が未定の場合、無限ループに陥ってしまって処理が終わりません。

しかし、今回の質問のように遅延評価されれば、整数を頭から評価していき、偶数だったら表示する、そしてその評価が100に達した時点でループを終了する、というように、無限の値も扱うことができるようになります。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/01/12 11:34

    遅延評価という言葉だけ知っていましたが、教えていただいて、実感できました。
    ありがとうございました。

    キャンセル

checkベストアンサー

+1

実際にLinqが評価されるのはforeach文が始まってからです。
そして、Whereメソッドのように結果全体を評価する必要がない場合には逐次評価が行われていきます。

このため、値1つずつ、Whereの評価、Traceの評価、Selectを通って、foreach文のボディの評価、とこのようになります。break文を直後に挟めば出力結果は2になります。

違う例を示しますと、OrderByメソッドは全体をソートするため、一旦それまでのメソッドチェーンを評価してソートし、次のメソッドに結果を渡します。
list.Where(x => x % 2 == 0).Trace().OrderBy(_ => _).Select(x => x * 10);とすれば結果は

2
4
6
8
20
40
60
80

になりますし、OrderByOrderByDescendingに変えれば

2
4
6
8
80
60
40
20


となります。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/01/12 11:33

    edo_m18さんも回答いただいたとおり、遅延評価ですね。
    流れがわかりました。
    ありがとうございました。

    キャンセル

0

yieldの不思議さ・特徴を教えようとしているコードのようにみえます。
したがって、そういうものと捉える必要があります。

ただ、理解する方法がないわけではありません。
MSSNのyieldの解説には次のような記述があります。

foreach ステートメントまたは LINQ クエリを使用することにより、Iterator メソッドを処理します。 foreach ループの各イテレーションは、Iterator メソッドを呼び出します。 yield return ステートメントが Iterator メソッドに到達すると、expression が返され、コードの現在の位置が保持されます。 次回、Iterator 関数が呼び出されると、この位置から実行が再開されます。

前半の説明はかなり難しいことを言っておりますが、ポイントは「 次回、Iterator 関数が呼び出されると、この位置から実行が再開されます。」の部分です。メソッドが途中で停止して、次の値を要求されたときにはじめて再実行されるので、呼び出されるたびに、Console.WriteLineが呼び出されるというわけです。

とても不思議な機能ですが、それゆえに便利に使えるやり方です。特にforeachの中身が重い処理の場合などは便利です。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/01/12 11:44

    ありがとうございました。
    yieldをどう使いこなすか、学習していかないといけませんね。

    キャンセル

0

以下のようにすれば期待通りになるはずです。お試しください。

public static IEnumerable<T> Trace<T>(this IEnumerable<T> source)
{
    foreach (var item in source)
    {
        Console.WriteLine(item);
        //yield return item;
    }
    return source;
}

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/01/12 11:45

    ありがとうございました。期待通りの出力結果です。
    yieldの特性を学習します。

    キャンセル

  • 2017/01/12 12:02

    デバッガはもちろん使える環境ですよね? であれば、ブレークポイントを 2 つの foreach ループの最初の行に設定してデバッグ実行してみると動きの違いが分かるのでやってみてください。yield return ステートメント を使用した反復子のメソッドが原因と言うことがわかると思います。

    キャンセル

  • 2017/01/12 12:31

    はい、デバッグして試してみます。ありがとうございます。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.33%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る

  • C#

    7710questions

    C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。