LINQスタイルの設定[終了]


21

私は毎日のプログラミングでLINQを使用するようになりました。実際、明示的なループを使用することはほとんどありません。しかし、構文のようなSQLはもう使用しないことがわかりました。拡張機能を使用するだけです。ではなく、言って:

from x in y select datatransform where filter 

私が使う:

x.Where(c => filter).Select(c => datatransform)

どのスタイルのLINQを好みますか?また、チームの他のどのスタイルに満足していますか?


5
ことは注目に値するかもしれ公式MSスタンスは、クエリ構文が望ましいということです。
R0MANARMY

1
最終的には問題ではありません。重要なのは、コードが理解できることです。ある場合には1つの形式の方がよく、別の場合には他の形式の方がよい場合があります。したがって、その時点で適切なものを使用してください。
ChrisF

2番目の例はラムダ構文と呼ばれ、95%の時間を使用しています。他の5%はクエリ構文を使用しています。これは、結合を行うときに、ラムダ構文の結合に移行しようとしていますが、他の人が指摘しているように、面倒です。
マフィンマン

回答:


26

残念ながら、MSDNのドキュメントごとのMicrosoftのスタンスは、クエリ構文が望ましいということです。クエリ構文は使用しませんが、常にLINQメソッド構文を使用しています。ワンライナークエリを思いのままに実行できるのが大好きです。比較:

var products = from p in Products
               where p.StockOnHand == 0
               select p;

に:

var products = Products.Where(p => p.StockOnHand == 0);

より速く、より少ない線、そして私の目にはきれいに見えます。クエリ構文は、すべての標準LINQ演算子もサポートしていません。私が最近行ったクエリの例は次のようになりました。

var itemInfo = InventoryItems
    .Where(r => r.ItemInfo is GeneralMerchInfo)
    .Select(r => r.ItemInfo)
    .Cast<GeneralMerchInfo>()
    .FirstOrDefault(r => r.Xref == xref);

私の知る限り、クエリ構文を使用して(可能な範囲で)このクエリを複製するには、次のようになります。

var itemInfo = (from r in InventoryItems
                where r.ItemInfo is GeneralMerchInfo
                select r.ItemInfo)
                .Cast<GeneralMerchInfo>()
                .FirstOrDefault(r => r.Xref == xref);

私にはもっと読みやすいとは思わないので、とにかくメソッド構文の使用方法を知る必要があります。個人的には、LINQが可能にし、可能な限りあらゆる状況で使用する宣言スタイルに本当に夢中になっています。適切な場合、メソッド構文を使用すると、次のようなことができます。

// projects an InventoryItem collection with total stock on hand for each GSItem
inventoryItems = repository.GSItems
    .Select(gsItem => new InventoryItem() {
        GSItem = gsItem,
        StockOnHand = repository.InventoryItems
            .Where(inventoryItem => inventoryItem.GSItem.GSNumber == gsItem.GSNumber)
            .Sum(r => r.StockOnHand)
     });

上記のコードは、適切なドキュメントなしでプロジェクトに参加する人にとって理解するのが難しく、LINQの背景がしっかりしていない場合は、とにかく理解できないかもしれません。それでも、メソッド構文は非常に強力な機能を公開しており、クエリをすばやく(コード行の観点から)投影して、そうでなければ多くの退屈なforeachループを必要とする複数のコレクションに関する集約情報を取得します。このような場合、メソッド構文は、得られるものに対して非常にコンパクトです。クエリ構文を使用してこれを実行しようとすると、かなり迅速に扱いにくくなる可能性があります。


select内で実行できるキャストですが、残念ながら、LINQメソッドを使用せずに上位Xレコードを取得するように指定することはできません。これは、1つのレコードだけが必要で、すべてのクエリを角かっこで囲む必要があることがわかっている場所では特に面倒です。
ジブ

2
レコードについては、Where()。Select()。Cast <>()の代わりにSelect(x => x.ItemInfo).OfType <GeneralMerchInfo>()を実行できます。 n * 2mではなく)。しかし、あなたは完全に正しいです、ラムダ構文は読みやすさの観点からはるかに優れています。
エドジェームズ

16

関数の構文は目に優しいと思います。唯一の例外は、3つ以上のセットを結合する必要がある場合です。Join()はすぐに狂ってしまいます。


同意します...私は、(指摘されているように)参加するときを除いて、拡張メソッドの外観と読みやすさを好みます。コンポーネントベンダー(Telerikなど)は、拡張メソッドを非常に多く使用しています。私が考えている例は、ASP.NET MVCのRadコントロールです。それらを使用/読むには、拡張メソッドを非常に使いこなす必要があります。
Catchops

これを言って来ました。結合が含まれていない限り、通常はラムダを使用します。結合が行われると、LINQ構文は読みやすくなる傾向があります。
ショーン

10

別の答えを追加するのは遅すぎますか?

私は大量のLINQ-to-objectsコードを作成しましたが、少なくともそのドメインでは、より単純なコードを作成する方を使用するために両方の構文を理解するのが良いと主張します-常にドット構文ではありません。

もちろん、ドット構文は時間があるIS進むべき道は-他の人は、これらの例のいくつかを提供しています。しかし、理解力は短期間で変化したと思います-悪いラップが与えられたとしても。したがって、理解が役立つと思われるサンプルを提供します。

数字置換パズルの解決策は次のとおりです(LINQPadを使用して記述されたソリューションですが、コンソールアプリでスタンドアロンにすることもできます)。

// NO
// NO
// NO
//+NO
//===
// OK

var solutions =
    from O in Enumerable.Range(1, 8) // 1-9
                    //.AsQueryable()
    from N in Enumerable.Range(1, 8) // 1-9
    where O != N
    let NO = 10 * N + O
    let product = 4 * NO
    where product < 100
    let K = product % 10
    where K != O && K != N && product / 10 == O
    select new { N, O, K };

foreach(var i in solutions)
{
    Console.WriteLine("N = {0}, O = {1}, K = {2}", i.N, i.O, i.K);
}

//Console.WriteLine("\nsolution expression tree\n" + solutions.Expression);

...出力:

N = 1、O = 6、K = 4

それほど悪くはありませんが、ロジックは直線的に流れており、単一の正しいソリューションが得られることがわかります。このパズルは、手で解決する簡単な十分:3>ことを推論N> 0、およびO> 4×N 8> =意味O10例最大(2手で試験にあります> = 4意味N行列5のためのO)。私は十分に迷っています-このパズルはLINQのイラスト目的で提供されています。

コンパイラー変換

これを同等のドット構文に変換するためにコンパイラが行うことはたくさんあります。通常の2番目以降のfromSelectManyは呼び出しに変換されますが、どちらもtransparent-identifiersを使用する射影付きの呼び出しにletなる句Selectがあります。これから説明するように、ドット構文でこれらの識別子に名前を付けることは、そのアプローチの可読性を奪います。

このコードをドット構文に変換する際にコンパイラーが行うことを公開するコツがあります。上記の2行のコメントを外し、再度実行すると、次の出力が得られます。

N = 1、O = 6、K = 4

ソリューション式ツリーSystem.Linq.Enumerable + d_ b8.SelectMany(O => Range(1、8)、(O、N)=> new <> f _AnonymousType0 2(O = O, N = N)).Where(<>h__TransparentIdentifier0 => (<>h__TransparentIdentifier0.O != <>h__TransparentIdentifier0.N)).Select(<>h__TransparentIdentifier0 => new <>f__AnonymousType12(<> h_ TransparentIdentifier0 = <> h _TransparentIdentifier0、NO = ((10 * <> h_ TransparentIdentifier0.N)+ <> h _TransparentIdentifier0.O)))。Select(<> h_ TransparentIdentifier1 => new <> f _AnonymousType2 2(<>h__TransparentIdentifier1 = <>h__TransparentIdentifier1, product = (4 * <>h__TransparentIdentifier1.NO))).Where(<>h__TransparentIdentifier2 => (<>h__TransparentIdentifier2.product < 100)).Select(<>h__TransparentIdentifier2 => new <>f__AnonymousType32(<> h_ TransparentIdentifier2 = <> h _TransparentIdentifier2、K =( <> h_ TransparentIdentifier2.product%10)))。Where(<> h _TransparentIdentifier3 =>(((<> h_ TransparentIdentifier3.K!= <> h _TransparentIdentifier3。<> h_ TransparentIdentifier2。<>h _TransparentIdentifier1。<> h_TransparentIdentifier0.O)AndAlso(<> h _TransparentIdentifier3.K!= <> h_ TransparentIdentifier3。<> h _TransparentIdentifier2。<> h_ TransparentIdentifier1。<> h _TransparentIdentifier0.N))AndAlso((<> h_ TransparentIdentifier3。<> h _TransparentIdentifier3。<> 2。_TransparentIdentifier product / 10)== <> h_ TransparentIdentifier3。<> h _TransparentIdentifier2。<> h_ TransparentIdentifier1。<> h _TransparentIdentifier0.O)))。Select(<> h_ TransparentIdentifier3 => new <> f _AnonymousType4`3(N = < > h_ TransparentIdentifier3。<> h _TransparentIdentifier2。<> h_ TransparentIdentifier1。<> h _TransparentIdentifier0.N、O = <> h_ TransparentIdentifier3。<> h_TransparentIdentifier2。<> h_ TransparentIdentifier1。<> h _TransparentIdentifier0.O、K = <> h__TransparentIdentifier3.K))

各LINQ演算子を新しい行に配置し、「話すことのできない」識別子を「話す」ことができるものに変換し、匿名型を使い慣れた形式に変更し、AndAlso式ツリーの用語を変更して&&、コンパイラーが同等の処理に到達するための変換を公開するドット構文で:

var solutions = 
    Enumerable.Range(1,8) // from O in Enumerable.Range(1,8)
        .SelectMany(O => Enumerable.Range(1, 8), (O, N) => new { O = O, N = N }) // from N in Enumerable.Range(1,8)
        .Where(temp0 => temp0.O != temp0.N) // where O != N
        .Select(temp0 => new { temp0 = temp0, NO = 10 * temp0.N + temp0.O }) // let NO = 10 * N + O
        .Select(temp1 => new { temp1 = temp1, product = 4 * temp1.NO }) // let product = 4 * NO
        .Where(temp2 => temp2.product < 100) // where product < 100
        .Select(temp2 => new { temp2 = temp2, K = temp2.product % 10 }) // let K = product % 10
        .Where(temp3 => temp3.K != temp3.temp2.temp1.temp0.O && temp3.K != temp3.temp2.temp1.temp0.N && temp3.temp2.product / 10 == temp3.temp2.temp1.temp0.O)
        // where K != O && K != N && product / 10 == O
        .Select(temp3 => new { N = temp3.temp2.temp1.temp0.N, O = temp3.temp2.temp1.temp0.O, K = temp3.K });
        // select new { N, O, K };

foreach(var i in solutions)
{
    Console.WriteLine("N = {0}, O = {1}, K = {2}", i.N, i.O, i.K);
}

実行すると、再び出力されることを確認できます:

N = 1、O = 6、K = 4

...しかし、このようなコードを書くことはありますか?

答えはNONBHN(いいえだけでなく、地獄も違います!)です-それはあまりにも複雑だからです。確かに「temp0」..「temp3」よりも意味のある識別子名を思いつくことができますが、ポイントはコードに何も追加しないということです-コードのパフォーマンスを向上させず、コードを読みやすくするために、彼らはコードをugいだけにし、あなたがそれを手でやっていたら、間違いなくあなたがそれを正しくする前に1、3回混乱させるでしょう。また、「名前ゲーム」をプレイすることは、意味のある識別子にとっては十分に難しいので、コンパイラがクエリの理解で提供してくれる名前ゲームからの脱却を歓迎します。

このパズルのサンプルは、あなたが真剣に考えるほど現実的ではないかもしれません。ただし、クエリの理解が必要な他のシナリオは存在します。

  • Joinand の複雑さGroupJoin:クエリ内包join句の範囲変数のスコープは、そうでなければドット構文でコンパイルされるかもしれない間違いを内包構文のコンパイル時エラーに変えます。
  • コンパイラーが内包表記変換に透明識別子を導入するときはいつでも、内包表記は価値があります。これには、複数のfrom句、joinjoin..into句、let句のいずれかの使用が含まれます。

理解構文を禁止している故郷のエンジニアリングショップを複数知っています。理解構文はツールであり、有用なツールであるため、これは残念だと思います。私はそれは、言ってのようにたくさんだと思う「あなたがノミを行うことができないことをドライバーでできることがあります。あなたはノミのようにドライバーを使用することができますので、ノミは王の法令の下で今後禁止されています。」


-1:うわー。OPは少しアドバイスを探していました。あなたは小説を作り上げました!これを少し強化してもらえますか?
ジムG.

8

私のアドバイスは、内包構文で式全体を実行できる場合に、クエリ内包構文を使用することです。つまり、私は好む:

var query = from c in customers orderby c.Name select c.Address;

var query = customers.OrderBy(c=>c.Name).Select(c=>c.Address);

しかし、私は好むだろう

int count = customers.Where(c=>c.City == "London").Count();

int count = (from c in customers where c.City == "London" select c).Count();

この2つを混合するのに適した構文を考え出せたらと思います。何かのようなもの:

int count = from c in customers 
            where c.City == "London" 
            select c 
            continue with Count();

しかし、悲しいことに私たちはしませんでした。

しかし基本的には、それは好みの問題です。あなたとあなたの同僚により良く見えるものをしてください。


3
または、「変数の説明」リファクタリングを使用して、理解を他のLINQ演算子呼び出しから分離することを検討できます。たとえば、var londonCustomers = from c in ...; int count = londonCustomers.Count();
devgeezer

3

SQLライクは開始する良い方法です。ただし、制限されているため(現在の言語がサポートする構成のみをサポートします)、最終的に開発者は拡張メソッドスタイルに移行します。

SQLのようなスタイルで簡単に実装できる場合があることに注意してください。

また、1つのクエリで両方の方法を組み合わせることができます。


2

クエリのように途中で変数を定義する必要がない限り、非クエリ構文を使用する傾向があります

from x in list
let y = x.DoExpensiveCalulation()
where y > 42
select y

しかし、私は次のような非クエリ構文を書きます

x.Where(c => filter)
 .Select(c => datatransform)

2

順序付けのため、私は常に拡張機能を使用します。あなたの簡単な例を考えてみましょう-SQLで、実際に最初に実行された場所を選択します。拡張メソッドを使用して作成する場合は、はるかに制御しやすくなります。私は何が提供されているかについてIntellisenseを取得し、それらが起こる順序で物事を書きます。


「クエリの内包表記」構文では、ページ上の順序は操作が行われる順序と同じであることがわかると思います。SQLとは異なり、LINQは最初に「選択」を行いません。
エリックリッパー

1

拡張機能も好きです。

おそらく、それは私の頭の中の構文の飛躍ではありません。

特にlinq apiを備えたサードパーティのフレームワークを使用している場合は、目にも読みやすいと感じます。


0

ここに私が従う経験則があります:

結合がある場合、ラムダよりもLINQ式を優先します。

結合を持つラムダは乱雑に見え、読みにくいと思います。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.