◆ メソッド通した順番
◆ Distinct は全部の要素を見ないでも処理できる 

LINQ で Where と Distinct の両方を使いたいときがあって どっちを先に書いたほうがいいんだろうと疑問に思いました

LINQ は遅延評価で .Select(e => e).Take(1) だとコレクションの全部みないで最初のひとつだけ Select の関数が実行されます
ということは 後ろから実行されてるの?
とか Distinct ってソートみたいに全部の要素が必要?
とか 色々気になるところがあったので簡単なサンプルを作ってみました

1,2,1,2,3 の int 型のリストを Distinct → 奇数で Where と 奇数で Where → Distinct します
それらを Take で 1 つだけ欲しい時と 2 つほしいときの 2 パターンです

using System;
using System.Threading;
using System.Collections.Generic;
using System.Linq;

public class Sample
{
    public static void Main()
    {
        Func<int, bool> x = v =>
        {
            Console.WriteLine($"where: {v}");
            return v % 2 == 1;
        };
        var list = new List<int> { 1, 2, 1, 2, 3 };
        list.Distinct(new Comparer()).Where(e => x(e)).Take(1).ToList();
        Console.WriteLine("--");
        list.Where(e => x(e)).Distinct(new Comparer()).Take(1).ToList();
        Console.WriteLine("--");
        list.Distinct(new Comparer()).Where(e => x(e)).Take(2).ToList();
        Console.WriteLine("--");
        list.Where(e => x(e)).Distinct(new Comparer()).Take(2).ToList();
    }
}

public class Comparer : IEqualityComparer<int>
{
    public bool Equals(int x, int y)
    {
        Console.WriteLine($"distinct: {x}, {y}");
        return x == y;
    }

    public int GetHashCode(int obj)
    {
        Console.WriteLine($"distinct: {obj}");
        return obj.GetHashCode();
    }
}
distinct: 1
where: 1
--
where: 1
distinct: 1
--
distinct: 1
where: 1
distinct: 2
where: 2
distinct: 1
distinct: 1, 1
distinct: 2
distinct: 2, 2
distinct: 3
where: 3
--
where: 1
distinct: 1
where: 2
where: 1
distinct: 1
distinct: 1, 1
where: 2
where: 3
distinct: 3

それぞれのメソッドの内部処理の実行時に Console.WriteLine する関数をセットしてます
Distinct は関数じゃなくて IEqualityComparer を実装したクラスのインスタンスを引数にするので Console.WriteLine する Comparer クラスを作ってます




.Take(1) だと 2 行しか出力がなく .Take(2) では長くなってます
Distinct は最後まで全部の要素を揃えなくても 1 つずつ要素を受け取ったときに今まで受け取ったものにあったらスキップするようなストリーミングな処理になってるみたいです
全部要素が必要になるとムダな処理が増えますからね
OrderBy のような仕方のないもの以外は最後まで見なくていいようになってるようです


実行されるのは普通にメソッドチェーンに書いた順番でした
最後の .Take(1) を先に見て 1 つ要素を持ってくるというのじゃなくて Where → Distinct → Take の順番で 1 要素ずつ処理して 1 つめの Take まで来たら これ以上続けないように止めるようにしてるのでしょう


Distinct はコンソールに出力された順番を見る限り GetHashCode 関数で得られたハッシュ値が過去の要素と一致したら Equals メソッドを使ってチェックするようです
ハッシュ値が違うか Equals メソッドの結果が false なら異なる扱いで true の場合はその要素は次のメソッドに行かずそこでキャンセルになってます

まとめ

順番はどっちが先でもいいものなら Where など後ろの処理は行わないフィルタするものを Select などフィルタしないものより先に持ってくると余計な処理が減らせます
.Select().Where() の順なら全部に Select と Where の処理をしますが .Where().Select() なら Where でフィルタしてしまった要素は Select の処理しなくて済みます

両方ともフィルタする Where と Distinct だと Distinct は基本複雑な比較はしなくて単純に等しいかの比較するだけが多いと思うので 重い処理の可能性が高い Where の処理数を減らせるように Where を後にしたほうがいいと思います

基本は重い処理は後に持ってきてやらなくて済む場合はやらないようにする ですね