結果をキャッシュしない方法でLINQを実装することにより、どのような利点が得られましたか?


20

これは、LINQを使用して足を濡らしている人々にとって既知の落とし穴です。

public class Program
{
    public static void Main()
    {
        IEnumerable<Record> originalCollection = GenerateRecords(new[] {"Jesse"});
        var newCollection = new List<Record>(originalCollection);

        Console.WriteLine(ContainTheSameSingleObject(originalCollection, newCollection));
    }

    private static IEnumerable<Record> GenerateRecords(string[] listOfNames)
    {
        return listOfNames.Select(x => new Record(Guid.NewGuid(), x));
    }

    private static bool ContainTheSameSingleObject(IEnumerable<Record>
            originalCollection, List<Record> newCollection)
    {
        return originalCollection.Count() == 1 && newCollection.Count() == 1 &&
                originalCollection.Single().Id == newCollection.Single().Id;
    }

    private class Record
    {
        public Guid Id { get; }
        public string SomeValue { get; }

        public Record(Guid id, string someValue)
        {
            Id = id;
            SomeValue = someValue;
        }
    }
}

これは、「False」を出力します。元のコレクションを作成するために指定された名前ごとに、選択関数が再評価され続け、結果のRecordオブジェクトが新たに作成されるためです。これを修正するにToListは、の最後にへの簡単な呼び出しを追加できますGenerateRecords

このように実装することで、マイクロソフトはどのような利点を得たいと考えましたか?

なぜ実装は結果を内部配列に単純にキャッシュしないのですか?何が起こっているかの特定の部分は、実行の遅延である場合がありますが、この動作がなくても実装できます。

LINQによって返されたコレクションの特定のメンバーが評価されると、内部参照/コピーを保持せずに、デフォルトの動作として同じ結果を再計算することによってどのような利点が提供されますか?

繰り返し再計算されるコレクションの同じメンバーのロジックに特定のニーズがある状況では、オプションのパラメーターを介して指定でき、デフォルトの動作は別の方法で実行できるようです。さらに、実行の遅延によって得られる速度の利点は、同じ結果を継続的に再計算するのにかかる時間によって最終的に削減されます。最後に、これはLINQを初めて使用する人にとっては混乱を招くブロックであり、最終的には誰のプログラムでも微妙なバグにつながる可能性があります。

これにはどのような利点があり、Microsoftはなぜこの一見非常に慎重な決定を下したのですか?


1
GenerateRecords()メソッドでToList()を呼び出すだけです。 return listOfNames.Select(x => new Record(Guid.NewGuid(), x)).ToList(); これにより、「キャッシュされたコピー」が得られます。問題が解決しました。
ロバートハーヴェイ

1
私は知っていますが、そもそもなぜこれが必要なのだろうと思っていました。
Panzercrisis

11
遅延評価には大きな利点があるため、少なくとも「おお、ちなみに、このレコードは最後に要求してから変更されました。これが新しいバージョンです」というコード例です。
ロバートハーベイ

ここ6か月でほぼ同じフレーズの質問を読んだと断言できましたが、今は見つかりません。:私は見つけることができる最も近いStackOverflowの上で2016年からだったstackoverflow.com/q/37437893/391656
Mr.Mindor

29
有効期限ポリシーのないキャッシュの名前は「メモリリーク」です。無効化ポリシーのないキャッシュの名前は「バグファーム」です。可能なすべてのLINQクエリで機能する、常に正しい有効期限および無効化ポリシーを提案しない場合、質問自体が回答します。
エリックリッパー

回答:


51

結果をキャッシュしない方法でLINQを実装することにより、どのような利点が得られましたか?

結果をキャッシュすることは、単に誰にとってもうまくいくとは限りません。少量のデータがある限り、すばらしい。よかったね。しかし、データがRAMよりも大きい場合はどうでしょうか?

LINQとは関係ありませんがIEnumerable<T>、一般的なインターフェイスとは関係ありません。

それは違いありFile.ReadAllLinesFile.ReadLines。1つはファイル全体をRAMに読み込み、もう1つは行ごとにファイルを提供するため、大きなファイルを処理できます(改行がある限り)。

どちらか.ToList()またはその.ToArray()上でシーケンス呼び出しを具体化することにより、キャッシュしたいすべてのものを簡単にキャッシュできます。しかし、ない人たちのものではない、それをキャッシュするには、我々はチャンス持っていないそうです。

また、関連するメモ:次をキャッシュする方法は?

IEnumerable<int> AllTheZeroes()
{
    while(true) yield return 0;
}

できません。それがIEnumerable<T>存在する理由です。


2
最後の例は、それが実際の無限級数(フィボナッチなど)であり、単なる無数のゼロのストリングではなく、特に興味深いものではない場合、より説得力があります。
ロバートハーヴェイ

23
@RobertHarveyそれは本当です、理解するロジックがまったくないときにゼロの無限のストリームであることが簡単にわかると思いました。
-nvoigt

2
int i=1; while(true) { i++; yield fib(i); }
ロバートハーヴェイ

2
私が考えていた例はEnumerable.Range(1,int.MaxValue)-使用するメモリ量の下限を計算するのは非常に簡単です。
クリス

4
私はの線に沿って見てきた他の事はwhile (true) return ...したwhile (true) return _random.Next();乱数の無限のストリームを生成します。
クリス

24

このように実装することで、マイクロソフトはどのような利点を得たいと考えましたか?

正しさ?つまり、列挙可能なコアは呼び出しの間に変わる可能性があります。キャッシュすると、誤った結果が生成され、「そのキャッシュをいつ/どのように無効にできますか?」全体が開かれます。

そして、LINQがもともとLINQをデータソース(エンティティフレームワークやSQLなど)に実行する手段として設計されたと考えると、enumerable データベースの機能なので変更されるでしょう

それに加えて、単一責任原則の懸念があります。クエリおよびキャッシュするコードを作成してからキャッシュを削除するよりも、機能するクエリコードを作成してその上にキャッシュを作成する方がはるかに簡単です。


3
ICollection存在することを言及する価値があるかもしれませんし、おそらくOPのIEnumerable動作を期待しているように動作します
-Caleth

IEnumerable <T>を使用して開いているデータベースカーソルを読み取る場合、ACIDトランザクションでデータベースを使用している場合、結果は変わらないはずです。
ダグ

4

LINQは関数型プログラミング言語人気のあるMonadパターンの一般的な実装であり、最初から意図されていたため、Monadは同じ呼び出しシーケンスが与えられたときに常に同じ値を生成するように制約されません(実際、その使用関数型プログラミングでは、純粋な関数の決定論的な動作を回避できるこのプロパティのため、まさに人気があります)。


4

言及されていないもう1つの理由は、ゴミの中間結果を作成せずに異なるフィルターと変換を連結できる可能性です。

これを例に取ります:

cars.Where(c => c.Year > 2010)
.Select(c => new { c.Model, c.Year, c.Color })
.GroupBy(c => c.Year);

LINQメソッドが結果をすぐに計算した場合、3つのコレクションがあります。

  • どこの結果
  • 結果を選択
  • GroupBy結果

そのうち最後の1つだけが重要です。中間結果にアクセスすることはできないため、中間結果を保存しても意味がありません。また、既にフィルタリングされ、年ごとにグループ化された車についてのみ知りたいのです。

これらの結果のいずれかを保存する必要がある場合、解決策は簡単です。呼び出しを分解して呼び出し.ToList()、変数に保存します。


副次的な注意点として、JavaScriptでは、Arrayメソッドは実際に結果をすぐに返すため、注意しないとメモリ消費量が増える可能性があります。


3

基本的に、このコード(文のGuid.NewGuid ()中にSelect記述)は非常に疑わしいものです。これは確かに何らかのコードの匂いです!

理論的には、Selectステートメントが新しいデータを作成するのではなく、既存のデータを取得することを必ずしも期待していません。Selectが複数のソースからのデータを結合して、さまざまな形状の結合コンテンツを生成したり、追加の列を計算したりすることは合理的ですが、それでも機能的かつ純粋であることが期待されます。NewGuid ()内側に置くと、機能せず純粋ではなくなります。

データの作成は、選択から離れて何らかのソートの作成操作に入れることができます。そのため、選択を純粋で再利用可能なままにすることができます。ある.ToList ()提案は。

ただし、明確にするために、この問題は、キャッシュの不足ではなく、選択内での作成の混合のようです。NewGuid()選択を内側に置くことは、プログラミングモデルの不適切な混合のように思えます。


0

遅延実行により、LINQコード(正確にはを使用してIEnumerable<T>)を記述するユーザーは、結果をすぐに計算してメモリに保存するかどうかを明示的に選択できます。言い換えれば、プログラマーは、アプリケーションに最適な計算時間とストレージスペースのトレードオフを選択できます。

大多数のアプリケーションはすぐに結果を必要とするので、それはLINQのデフォルトの動作であるはずです。しかし、List<T>.ConvertAllこの動作を提供し、フレームワークが作成されてから実行されている他の多くのAPI(例:)がありますが、LINQが導入されるまで実行を延期する方法はありませんでした。他の答えが示しているように、これは、即時実行を使用した場合に不可能な(使用可能なすべてのストレージを使い果たすことによって)特定のタイプの計算を可能にするための前提条件です。

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