web-dev-qa-db-ja.com

LINQスタイル設定

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

from x in y select datatransform where filter 

私が使う:

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

あなたはどのスタイルのLINQを好みますか?また、チームの他のメンバーは何に慣れていますか?

22
Erin

残念ながら、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ループを必要とする複数のコレクションに関する集約情報を取得します。このような場合、メソッド構文は、それを利用するために非常にコンパクトです。クエリ構文でこれを実行しようとすると、かなり手に負えなくなる可能性があります。

27
klir2m

関数の構文が目に優しいと思います。唯一の例外は、3つ以上のセットに参加する必要がある場合です。 Join()は非常に早く気が狂います。

16
John Kraft

別の回答を追加するのに手遅れになることはありませんか?

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

もちろん、ドット構文[〜#〜]が[〜#〜]になる方法もあります-他の人がこれらのケースのいくつかを提供しています;ただし、理解が短めに変更されたと思います。そこで、理解度が役立つと思うサンプルを提供します。

数字置換パズルの解決策は次のとおりです(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> = O> = 4を意味します。つまり、手動でテストするケースは最大10です(Nの場合は2、Oの場合は5)。私は十分に迷いました-このパズルはLINQの説明のために提供されています。

コンパイラの変換

これを同等のドット構文に変換するためにコンパイラが行うことはたくさんあります。通常の秒以降のfrom句はSelectMany呼び出しに変換されますlet句がSelectプロジェクション付きの呼び出し。どちらも transparent-identifiers を使用します。これから説明するように、ドット構文でこれらの識別子に名前を付ける必要があると、そのアプローチの読みやすさが失われます。

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

N = 1、O = 6、K = 4

ソリューション式ツリーSystem.Linq.Enumerable + d _b8.SelectMany(O => Range(1、8)、(O、N)=> new <> f_ AnonymousType02(O = O, N = N)).Where(<>h__TransparentIdentifier0 => (<>h__TransparentIdentifier0.O != <>h__TransparentIdentifier0.N)).Select(<>h__TransparentIdentifier0 => new <>f__AnonymousType1 2(<> h _TransparentIdentifier0 = <> h_ TransparentIdentifier0、NO =((10 * <> h _TransparentIdentifier0.N)+ <> h_ TransparentIdentifier0.O)))。Select(<> h _TransparentIdentifier1 => new <> f_ AnonymousType22(<>h__TransparentIdentifier1 = <>h__TransparentIdentifier1, product = (4 * <>h__TransparentIdentifier1.NO))).Where(<>h__TransparentIdentifier2 => (<>h__TransparentIdentifier2.product < 100)).Select(<>h__TransparentIdentifier2 => new <>f__AnonymousType3 2(<> 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_TransparentIdentifier2.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」よりも意味のある識別子名を考え出すことができますが、重要なのは、コードに何も追加されないことです。コードのパフォーマンスが向上するわけではありません。コードを読みやすくし、コードを醜くするだけです。手作業で行っている場合は、正しくするまでに1〜3回失敗することは間違いありません。また、「名前ゲーム」をプレイすることは、意味のある識別子にとって十分に難しいので、コンパイラがクエリ内包で提供する名前ゲームからの脱却を歓迎します。

このパズルのサンプルは現実の世界では十分ではない可能性があります。ただし、クエリの理解が優れているシナリオは他にもあります。

  • JoinGroupJoinの複雑さ:クエリ内包の範囲変数のスコープjoin句は、ドット構文でコンパイルされる可能性のある誤りを、内包構文のコンパイル時エラーに変換します。 。
  • コンパイラーが内包変換に透明な識別子を導入するときはいつでも、内包は価値があります。これには、複数のfrom句、join&_join..into_句、およびlet句の使用が含まれます。

私の故郷には、outlawed内包構文を持つエンジニアリングショップが複数あります。理解構文はツールであり、有用なものであるため、これは残念だと思います。 「ドライバーでできるのはノミではできないこと。ドライバーをノミとして使うことができるので、ノミは今後禁止されます。王の布告。」

11
devgeezer

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

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();

しかし、残念ながら私たちはしませんでした。

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

9
Eric Lippert

SQLのようなものから始めるのが良いでしょう。ただし、制限があるため(現在の言語でサポートされている構文のみがサポートされます)、最終的に開発者は拡張メソッドスタイルに移行します。

SQL風のスタイルで簡単に実装できる場合があることに注意したい。

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

3
SiberianGuy

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

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

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

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

順序付けのため、私は常に拡張関数を使用します。簡単な例を見てみましょう-SQLでは、selectを最初に記述しました-実際には、最初に実行された場所ですが。拡張メソッドを使って書くとき、私はずっとコントロールしていると感じます。私は何が提供されているのかについてIntellisenseを取得し、それらが発生する順序で物事を書きます。

2
DeadMG

拡張機能も好きです。

多分それは私の心の中の構文の飛躍の少ないものを引き起こします。

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

1
Erion

これが私が従うヒューリスティックです:

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

ジョインのあるラムダは乱雑に見え、読みにくいと思います。

0
Jim G.