web-dev-qa-db-ja.com

where条件とcontinueガード句を使用したforeachループのフィルタリング

一部のプログラマーがこれを使用するのを見てきました。

foreach (var item in items)
{
    if (item.Field != null)
        continue;

    if (item.State != ItemStates.Deleted)
        continue;

    // code
}

私が通常使用する場所の代わりに:

foreach (var item in items.Where(i => i.Field != null && i.State != ItemStates.Deleted))
{
    // code
}

両方の組み合わせを見たこともあります。 「継続」、特により複雑な条件での読みやすさが本当に好きです。パフォーマンスにも違いはありますか?データベースクエリがあると仮定します。通常のリストはどうですか?

24
Paprik

これは コマンド/クエリの分離 を使用する適切な場所だと思います。例えば:

// query
var validItems = items.Where(i => i.Field != null && i.State != ItemStates.Deleted);
// command
foreach (var item in validItems) {
    // do stuff
}

これにより、クエリ結果に適切な自己文書化名を付けることもできます。また、データのクエリのみ、またはデータの変更のみを行うコードをリファクタリングする方が、両方を実行しようとする混合コードよりもはるかに簡単であるため、リファクタリングの機会を確認するのにも役立ちます。

デバッグ時には、foreachの前で中断して、validItemsの内容が期待どおりに解決されるかどうかをすばやく確認できます。必要がない限り、ラムダに足を踏み入れる必要はありません。ラムダにステップインする必要がある場合は、別の関数に分解して、代わりにステップ実行することをお勧めします。

パフォーマンスに違いはありますか?クエリがデータベースによってサポートされている場合、LINQバージョンにはpotentialが高速に実行されます。これは、SQLクエリmayの方が効率的です。 LINQ to Objectsの場合、実際のパフォーマンスの違いはわかりません。いつものように、事前に最適化を予測するのではなく、コードをプロファイリングして、実際に報告されているボトルネックを修正します。

65

もちろん、パフォーマンスには違いがあります。.Where()を使用すると、すべてのアイテムに対してデリゲート呼び出しが行われます。ただし、パフォーマンスについてはまったく心配しません。

  • デリゲートの呼び出しに使用されるクロックサイクルは、コレクションを反復処理して条件をチェックする残りのコードが使用するクロックサイクルと比較してごくわずかです。

  • デリゲートを呼び出すことによるパフォーマンスの低下は、数クロックサイクルのオーダーであり、幸いにも、個々のクロックサイクルについて心配する必要があった時代をはるかに超えています。

何らかの理由でクロックサイクルレベルでパフォーマンスが本当に重要である場合、_List<Item>_の代わりに_IList<Item>_を使用して、コンパイラーが直接(およびインライン可能)を利用できるようにします)仮想呼び出しの代わりに呼び出すため、実際にはstructである_List<T>_のイテレータをボックス化する必要はありません。しかし、それは本当に些細なことです。

(少なくとも理論的には)RDBMSにフィルターを送信する可能性があるため、データベースクエリは別の状況です。これにより、パフォーマンスが大幅に向上します。一致する行だけがRDBMSからプログラムに移動します。しかし、linqを使用する必要があると思うので、この式をそのままRDBMSに送信できるとは思いません。

if(x) continue;の利点は、このコードをデバッグする必要がある瞬間に実際にわかります。if() sとcontinuesのシングルステップはうまく機能します。フィルタリングデリゲートにシングルステップで入るのは面倒です。

7
Mike Nakis