読み取りループを終了する方法はどれですか?


13

読むアイテムの数が不明なリーダーを繰り返し処理する必要があり、そのための唯一の方法は、最後まで読み続けることです。

これは多くの場合、無限ループが必要な場所です。

  1. ブロック内のどこかに or ステートメントtrueがなければならないことを示すalways があります。breakreturn

    int offset = 0;
    while(true)
    {
        Record r = Read(offset);
        if(r == null)
        {
            break;
        }
        // do work
        offset++;
    }
  2. あり、二重ループ方式の読み取りが。

    Record r = Read(0);
    for(int offset = 0; r != null; offset++)
    {
        r = Read(offset);
        if(r != null)
        {
            // do work
        }
    }
  3. 単一のwhileループがあります。すべての言語がこの方法をサポートしているわけではありません

    int offset = 0;
    Record r = null;
    while((r = Read(++offset)) != null)
    {
        // do work
    }

どのアプローチがバグを引き起こす可能性が最も低く、最も読みやすく、一般的に使用されているのか疑問に思っています。

これらのいずれかを書かなければならないたびに、「より良い方法がなければならないと思う。


2
なぜオフセットを維持しているのですか?ほとんどのストリームリーダーでは、単に「次を読む」ことができませんか?
ロバートハーベイ

私の現在のニーズでは、@ RobertHarveyには、オフセットを使用して結果をページ分割する基礎となるSQLクエリがあります。空の結果を返すまで、クエリ結果の長さはわかりません。しかし、質問のためにそれは本当に要件ではありません。
Reactgular

3
混乱しているようです-質問のタイトルは無限ループに関するものですが、質問テキストはすべてループの終了に関するものです。(構造化プログラミングの時代からの)古典的な解決策は、事前読み取りを行い、データがある間にループし、ループ内の最後のアクションとして再度読み取ることです。それは単純で(バグ要件を満たす)、最も一般的です(50年にわたって書かれているため)。最も読みやすいのは意見の問題です。
andy256

@ andy256混乱は私にとってコーヒー前の状態です。
Reactgular

1
ああ、正しい手順は1)キーボードを避けながらコーヒーを飲む、2)コーディングループを開始する、です。
andy256

回答:


49

ここに戻ります。あなたはコードのきびきびとした詳細に集中しているが、大きな絵は見当たらない。ループの例の1つを見てみましょう。

int offset = 0;
while(true)
{
    Record r = Read(offset);
    if(r == null)
    {
        break;
    }
    // do work
    offset++;
}

何ですか このコード意味はですか?意味は「ファイル内の各レコードに何らかの作業を行う」です。しかし、それはコードどのように見えるかではありません。コードは「オフセットを維持します。ファイルを開きます。終了条件なしでループに入ります。レコードを読み取ります。ヌル性をテストします。」作業に着手する前にすべてのこと!あなたが尋ねるべき質問は、「このコードの外観をそのセマンティクスと一致させるにどうすればよいですか?です。このコードは次のようになります。

foreach(Record record in RecordsFromFile())
    DoWork(record);

これで、コードは意図どおりになりました。 メカニズムをセマンティクスから分離する。元のコードでは、メカニズム-ループの詳細-セマンティクス-各レコードに対して行われる作業を混同します。

今、私たちは実装する必要があります RecordsFromFile()。それを実装する最良の方法は何ですか? 誰も気にしない?これは誰もが見ているコードではありません。基本的なメカニズムコードであり、その10行の長さです。好きなように書いてください。これはどう?

public IEnumerable<Record> RecordsFromFile()
{
    int offset = 0;
    while(true)
    {
        Record record = Read(offset);
        if (record == null) yield break;
        yield return record;
        offset += 1;
    }
}

今、私たちは 遅延計算された一連のレコードをているので、あらゆる種類のシナリオが可能になります。

foreach(Record record in RecordsFromFile().Take(10))
    DoWork(record);

foreach(Record record in RecordsFromFile().OrderBy(r=>r.LastName))
    DoWork(record);

foreach(Record record in RecordsFromFile().Where(r=>r.City == "London")
    DoWork(record);

等々。

ループを書くときはいつでも、「このループはメカニズムのように、またはコードの意味のように読まれますか?」と自問してください。答えが「メカニズムのようなもの」である場合、そのメカニズムを独自のメソッドに移動し、コードを記述して、意味をより明確にします。


3
最後に賢明な答えを+1します。これはまさに私がやることです。ありがとう。
Reactgular

1
「そのメカニズムを独自のメソッドに移動してみてください」 - メソッドの抽出リファクタリングに似ているようですね。「メソッドの目的を説明する名前のフラグメントにメソッドを入れます。」
gnat

2
@gnat:私が提案しているのは、「メソッドを抽出する」よりも少し複雑です。メソッドを抽出することは、コードをそのセマンティクスに似たものにするのに間違いなく良いステップです。ポリシーとメカニズムを分離しておくことに目を向けて、メソッドの抽出を思慮深く行うことをお勧めします。
エリックリッパー

1
@gnat:その通り!この場合、抽出したい詳細は、ポリシーを維持しながら、ファイルからすべてのレコードを読み取るメカニズムです。ポリシーは「すべてのレコードで何らかの作業を行う必要がある」ことです。
エリックリッパー

1
そうですか。そうすれば、読みやすく保守しやすくなります。このコードを勉強し、私は政策やメカニズムに個別に焦点を当てることができ、それが注意を分割するために私を強制するものではありません
ブヨ

19

無限ループは必要ありません。C#の読み取りシナリオでは決して必要になることはありません。あなたが本当にオフセットを維持する必要があると仮定して、これは私の好ましいアプローチです:

Record r = Read(0);
offset=1;
while(r != null)
{
    // Do work
    r = Read(offset);
    offset++
}

このアプローチは、リーダーのセットアップ手順があるため、2つのreadメソッド呼び出しがあることを認識しています。while条件は、読者に全くデータがないだけの場合に、ループの先頭です。


6

まあそれはあなたの状況に依存します。しかし、私が考えることができるより「C#っぽい」ソリューションの1つは、組み込みのIEnumerableインターフェイスとforeachループを使用することです。IEnumeratorのインターフェイスは、MoveNextをtrueまたはfalseで呼び出すだけなので、サイズは不明です。次に、終了ロジックが1回(列挙子で)書き込まれます。複数の場所で繰り返す必要はありません。

MSDNはIEnumerator <T>の例を提供します。IEnumerator <T>を返すIEnumerable <T>も作成する必要があります。


コード例を教えてください。
Reactgular

おかげで、これは私がやることに決めたものです。無限ループが発生するたびに新しいクラスを作成するとは考えられませんが、問題は解決します。
Reactgular

ええ、私はあなたが何を意味するか知っています-多くのオーバーヘッド。イテレータの真の天才/問題は、コレクションに対して2つのことを同時に繰り返すことができるように設定されていることです。多くの場合、その機能は必要ないので、オブジェクトのラッパーにIEnumerableとIEnumeratorの両方を実装させるだけで済みます。別の見方をすれば、繰り返し処理しているものの基礎となるコレクションが、受け入れられているC#パラダイムを念頭に置いて設計されていないということです。そしてそれは大丈夫です。イテレータの追加のボーナスは、すべての並列LINQを無料で入手できることです!
Jトラナ

2

初期化、条件、およびインクリメントの操作がある場合、C、C ++、C#などの言語のforループを使用します。このような:

for (int offset = 0, Record r = Read(offset); r != null; r = Read(++offset)){
    // loop here!
}

または、これが読みやすいと思われる場合。私は個人的に最初のものを好みます。

for (int offset = 0, Record r = Read(offset); r != null; offset++, r = Read(offset)){
    // loop here!
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.