LINQ .Any VS .Exists-違いは何ですか?


413

コレクションでLINQを使用して、次のコード行の違いは何ですか?

if(!coll.Any(i => i.Value))

そして

if(!coll.Exists(i => i.Value))

アップデート1

逆アセンブルする.Existsとコードがないように見えます。

アップデート2

誰もがなぜこのコードがないのか知っていますか?


9
コンパイルしたコードはどのように見えますか?どのように分解しましたか?ildasm?あなたは何を見つけることを期待していましたが、見つかりませんでしたか?
Meinersbur 2010

回答:


423

ドキュメントを見る

List.Exists(Objectメソッド-MSDN

List(T)に、指定された述語で定義された条件に一致する要素が含まれているかどうかを判断します。

これは.NET 2.0以降、つまりLINQ以前に存在します。述語デリゲートと共に使用することを意味しますが、ラムダ式には下位互換性があります。また、Listだけがこれを持っています(IListも含みません)

IEnumerable.Any(拡張メソッド-MSDN)

シーケンスの要素が条件を満たすかどうかを決定します。

これは.NET 3.5の新機能であり、引数としてFunc(TSource、bool)を使用するため、ラムダ式およびLINQで使用することを目的としていました。

動作では、これらは同じです。


4
後で別のスレッド投稿し、.NET 2 List<>インスタンスメソッドのLinqの「同等のもの」をすべてリストしました。
Jeppe Stig Nielsen

201

違いは、AnyはIEnumerable<T>System.Linq.Enumerableで定義されたすべての拡張メソッドであることです。任意のIEnumerable<T>インスタンスで使用できます。

Existsは拡張メソッドではないようです。私の推測では、collはタイプList<T>です。存在する場合、ExistsはAnyと非常によく機能するインスタンスメソッドです。

つまりメソッドは基本的に同じです。一方は他方より一般的です。

  • Anyには、パラメーターをとらず、列挙型の項目を単に検索するオーバーロードもあります。
  • Existsにはそのような過負荷はありません。

13
よく(+1)を入れます。List <T> .Existsは.Net 2以降に存在しますが、一般的なリストでのみ機能します。IEnumerable <T> .Anyは、列挙可能なコレクションで機能する拡張機能として.Net 3に追加されました。プロパティであるList <T> .CountやIEnumerable <T> .Count()-メソッドのような同様のメンバーもあります。
キース、

51

TLDR; パフォーマンスの面で時間Anyがかかるようです(両方の値をほぼ同時に評価するように適切に設定した場合)

        var list1 = Generate(1000000);
        var forceListEval = list1.SingleOrDefault(o => o == "0123456789012");
        if (forceListEval != "sdsdf")
        {
            var s = string.Empty;
            var start2 = DateTime.Now;
            if (!list1.Exists(o => o == "0123456789012"))
            {
                var end2 = DateTime.Now;
                s += " Exists: " + end2.Subtract(start2);
            }

            var start1 = DateTime.Now;
            if (!list1.Any(o => o == "0123456789012"))
            {
                var end1 = DateTime.Now;
                s +=" Any: " +end1.Subtract(start1);
            }

            if (!s.Contains("sdfsd"))
            {

            }

テストリストジェネレーター:

private List<string> Generate(int count)
    {
        var list = new List<string>();
        for (int i = 0; i < count; i++)
        {
            list.Add( new string(
            Enumerable.Repeat("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 13)
                .Select(s =>
                {
                    var cryptoResult = new byte[4];
                    new RNGCryptoServiceProvider().GetBytes(cryptoResult);
                    return s[new Random(BitConverter.ToInt32(cryptoResult, 0)).Next(s.Length)];
                })
                .ToArray())); 
        }

        return list;
    }

1,000万件のレコード

「すべて:00:00:00.3770377存在:00:00:00.2490249」

5Mレコード

「すべて:00:00:00.0940094存在:00:00:00.1420142」

100万件のレコード

「すべて:00:00:00.0180018存在:00:00:00.0090009」

500kの場合(私はまた、最初に実行されたものに関連付けられている追加の操作がないかどうかを確認するために評価される順序を反転しました。)

"存在:00:00:00.0050005任意:00:00:00.0100010"

10万件のレコード

「存在:00:00:00.0010001任意:00:00:00.0020002」

Any速度は2倍遅くなるようです。

編集: 500万レコードと1000万レコードの場合、リストの生成方法を変更したところ、Exists突然Anyテストが遅くなり、テスト方法に問題があることがわかりました。

新しいテストメカニズム:

private static IEnumerable<string> Generate(int count)
    {
        var cripto = new RNGCryptoServiceProvider();
        Func<string> getString = () => new string(
            Enumerable.Repeat("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 13)
                .Select(s =>
                {
                    var cryptoResult = new byte[4];
                    cripto.GetBytes(cryptoResult);
                    return s[new Random(BitConverter.ToInt32(cryptoResult, 0)).Next(s.Length)];
                })
                .ToArray());

        var list = new ConcurrentBag<string>();
        var x = Parallel.For(0, count, o => list.Add(getString()));
        return list;
    }

    private static void Test()
    {
        var list = Generate(10000000);
        var list1 = list.ToList();
        var forceListEval = list1.SingleOrDefault(o => o == "0123456789012");
        if (forceListEval != "sdsdf")
        {
            var s = string.Empty;

            var start1 = DateTime.Now;
            if (!list1.Any(o => o == "0123456789012"))
            {
                var end1 = DateTime.Now;
                s += " Any: " + end1.Subtract(start1);
            }

            var start2 = DateTime.Now;
            if (!list1.Exists(o => o == "0123456789012"))
            {
                var end2 = DateTime.Now;
                s += " Exists: " + end2.Subtract(start2);
            }

            if (!s.Contains("sdfsd"))
            {

            }
        }

Edit2:わかりました。テストデータの生成による影響を排除するために、すべてをファイルに書き込んで、そこから読み取ります。

 private static void Test()
    {
        var list1 = File.ReadAllLines("test.txt").Take(500000).ToList();
        var forceListEval = list1.SingleOrDefault(o => o == "0123456789012");
        if (forceListEval != "sdsdf")
        {
            var s = string.Empty;
            var start1 = DateTime.Now;
            if (!list1.Any(o => o == "0123456789012"))
            {
                var end1 = DateTime.Now;
                s += " Any: " + end1.Subtract(start1);
            }

            var start2 = DateTime.Now;
            if (!list1.Exists(o => o == "0123456789012"))
            {
                var end2 = DateTime.Now;
                s += " Exists: " + end2.Subtract(start2);
            }

            if (!s.Contains("sdfsd"))
            {
            }
        }
    }

1,000万

「すべて:00:00:00.1640164存在:00:00:00.0750075」

500万

「すべて:00:00:00.0810081存在:00:00:00.0360036」

100万

「すべて:00:00:00.0190019存在:00:00:00.0070007」

50万

「すべて:00:00:00.0120012存在:00:00:00.0040004」

ここに画像の説明を入力してください


3
信用はありませんが、私はこれらのベンチマークに懐疑的です。数字を見てください:すべての結果には再帰が発生しています(3770377:2490249)。少なくとも私にとって、それは何かが正しくないことの確かな兆候です。ここでの計算は100%確実ではありませんが、繰り返し発生するパターンの確率は、値あたり999 ^ 999分の1(または999!多分?)だと思います。したがって、連続して8回発生する可能性はごくわずかです。これは、ベンチマークにDateTimeを使用しているためだと思います
Jerri Kangasniemi 16

@JerriKangasniemi同じ操作を単独で繰り返すと、常に同じ時間がかかるはずです。同じ操作を複数回繰り返す場合も同様です。何故それがDateTimeだと言うのですか?
Matas Vaitkevicius 16

もちろんそうです。それでも問題は、500kの呼び出しで、たとえば0120012秒かかることはほとんどあり得ないことです。そして、それが完全に線形であり、したがって数字を非常にうまく説明する場合、100万回の呼び出しは0240024秒(2倍の長さ)かかりますが、そうではありません。100万回の呼び出しには58、(3)%500kより長く、10Mには102,5%より5M長くかかります。したがって、これは線形関数ではないため、数値をすべて再帰するのに本当に合理的ではありません。高精度のタイマーを使用していないため、過去に問題が発生したため、DateTimeについて説明しました。
Jerri Kangasniemi 16

2
@JerriKangasniemiそれを修正して回答を投稿することをお勧めします
Matas Vaitkevicius

1
結果を正しく読んでいると、AnyはExistsの速度の約2〜3倍に過ぎないと報告されました。「どのようなものでも2倍遅くなるように見える」というあなたの主張をデータがどのように穏やかにサポートしているかはわかりません。少し遅い、確かに、桁違いではありません。
Suncat2000

16

ベンチマークに関するMatasの回答の続きとして。

TL / DR:Exists()とAny()は同等に高速です。

まず、ストップウォッチを使用したベンチマークは正確ではありませんが(series0neの別の回答を参照してください。ただし、トピックは類似しています)、DateTimeよりもはるかに正確です。

本当に正確な測定値を取得する方法は、パフォーマンスプロファイリングを使用することです。しかし、2つのメソッドのパフォーマンスが互いにどの程度合っているかを理解する1つの方法は、両方のメソッドをロードして実行し、それぞれの最速の実行時間を比較することです。そうすれば、それは本当にJITingやその他のノイズは私たちに悪い測定値を与える(とそれがいることを問題ではない両方の実行が「されているので、)均等misguiding意味で」。

static void Main(string[] args)
    {
        Console.WriteLine("Generating list...");
        List<string> list = GenerateTestList(1000000);
        var s = string.Empty;

        Stopwatch sw;
        Stopwatch sw2;
        List<long> existsTimes = new List<long>();
        List<long> anyTimes = new List<long>();

        Console.WriteLine("Executing...");
        for (int j = 0; j < 1000; j++)
        {
            sw = Stopwatch.StartNew();
            if (!list.Exists(o => o == "0123456789012"))
            {
                sw.Stop();
                existsTimes.Add(sw.ElapsedTicks);
            }
        }

        for (int j = 0; j < 1000; j++)
        {
            sw2 = Stopwatch.StartNew();
            if (!list.Exists(o => o == "0123456789012"))
            {
                sw2.Stop();
                anyTimes.Add(sw2.ElapsedTicks);
            }
        }

        long existsFastest = existsTimes.Min();
        long anyFastest = anyTimes.Min();

        Console.WriteLine(string.Format("Fastest Exists() execution: {0} ticks\nFastest Any() execution: {1} ticks", existsFastest.ToString(), anyFastest.ToString()));
        Console.WriteLine("Benchmark finished. Press any key.");
        Console.ReadKey();
    }

    public static List<string> GenerateTestList(int count)
    {
        var list = new List<string>();
        for (int i = 0; i < count; i++)
        {
            Random r = new Random();
            int it = r.Next(0, 100);
            list.Add(new string('s', it));
        }
        return list;
    }

上記のコードを4回実行すると(1 000 Exists()を実行Any()し、1 000 000の要素を持つリストで)、メソッドがほぼ同じくらい高速であることを確認するのは難しくありません。

Fastest Exists() execution: 57881 ticks
Fastest Any() execution: 58272 ticks

Fastest Exists() execution: 58133 ticks
Fastest Any() execution: 58063 ticks

Fastest Exists() execution: 58482 ticks
Fastest Any() execution: 58982 ticks

Fastest Exists() execution: 57121 ticks
Fastest Any() execution: 57317 ticks

わずかな違いがあります、バックグラウンドノイズでは説明できないほど小さい違いです。私の推測では、1万人または10万人Exists()Any()すると、そのわずかな差は多かれ少なかれ解消されるでしょう。


これについて整然とするために、10000、100000、1000000を実行することをお勧めします。また、平均値ではなく最小値を使用する理由を教えてください。
Matas Vaitkevicius 16

2
最小値は、各メソッドの最も速い実行(=おそらく最小量のバックグラウンドノイズ)を比較したいためです。私はそれを後で繰り返しますが、より多くの反復でそれを行うかもしれません(上司がバックログを処理する代わりにこれを行うために私にお金を払いたくないのではないかと思います)
Jerri Kangasniemi

私はポール・リンドバーグに尋ねました、そして彼はそれは大丈夫だと言っています;)最小に関しては私はあなたの推論を見ることができますが、より正統なアプローチは平均en.wikipedia.org/wiki/Algorithmic_efficiency#Practice
Matas Vaitkevicius

9
投稿したコードが実際に実行したコードである場合、両方の測定でExistsを呼び出すため、同様の結果が得られるのは当然のことです。;)
Simon Touchtech 2017年

ええ、ええ、私もあなたがそれを言うのを見ました。私の処刑ではありませんが。これは、私が比較しているものの取り除かれた概念として意図されたにすぎません。:P
Jerri Kangasniemi 2017

4

さらに、これはValueがbool型の場合にのみ機能します。通常、これは述語とともに使用されます。任意の述語は一般的に使用され、特定の条件を満たす要素があるかどうかを確認します。ここでは、要素iからブールプロパティへのマップを行っています。Valueプロパティがtrueである「i」を検索します。完了すると、メソッドはtrueを返します。


3

上記のように測定値を修正すると、AnyとExists、および平均を追加すると、次の出力が得られます。

Executing search Exists() 1000 times ... 
Average Exists(): 35566,023
Fastest Exists() execution: 32226 

Executing search Any() 1000 times ... 
Average Any(): 58852,435
Fastest Any() execution: 52269 ticks

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