LINQ: Tidak Ada vs Semua Jangan

272

Seringkali saya ingin memeriksa apakah nilai yang diberikan cocok dengan satu dalam daftar (mis. Ketika memvalidasi):

if (!acceptedValues.Any(v => v == someValue))
{
    // exception logic
}

Baru-baru ini, saya perhatikan ReSharper meminta saya untuk menyederhanakan pertanyaan ini menjadi:

if (acceptedValues.All(v => v != someValue))
{
    // exception logic
}

Jelas, ini identik secara logis, mungkin sedikit lebih mudah dibaca (jika Anda telah melakukan banyak matematika), pertanyaan saya adalah: apakah ini menghasilkan hit kinerja?

Rasanya seperti itu seharusnya (yaitu .Any()terdengar seperti itu arus pendek, sedangkan .All()terdengar seperti itu tidak), tapi saya tidak punya apa-apa untuk membuktikan hal ini. Adakah yang memiliki pengetahuan yang lebih dalam tentang apakah pertanyaan akan menyelesaikan hal yang sama, atau apakah ReSharper membuat saya tersesat?

Menandai
sumber
6
Sudahkah Anda mencoba membongkar kode Linq untuk melihat apa yang dilakukannya?
RQDQ
9
Dalam hal ini saya benar-benar akan pergi dengan if (! AcceptValues.Contains (someValue)), tapi tentu saja ini bukan pertanyaannya :)
csgero
2
@csgero saya setuju. Di atas adalah penyederhanaan (mungkin penyederhanaan berlebihan) dari logika yang sebenarnya.
Markus
1
"Rasanya seperti itu seharusnya (yaitu. Setiap () terdengar seperti itu sirkuit pendek, sedangkan. Semua () terdengar seperti tidak)" - Tidak untuk siapa pun dengan intuisi suara. Itu adalah kesetaraan logis yang Anda catat menyiratkan bahwa mereka sama-sama pendek. Pemikiran sesaat mengungkapkan bahwa Semua dapat berhenti begitu kasus yang tidak memenuhi syarat ditemukan.
Jim Balter
3
Saya tidak setuju secara universal dengan ReSharper dalam hal ini. Tulis kereta pemikiran yang masuk akal. Jika Anda ingin membuang perkecualian jika item yang diperlukan adalah hilang: if (!sequence.Any(v => v == true)). Jika Anda ingin melanjutkan hanya jika semuanya sesuai dengan spesifikasi tertentu: if (sequence.All(v => v < 10)).
Timo

Jawaban:

344

Implementasi Allmenurut ILSpy (seperti pada saya benar-benar pergi dan melihat, daripada "well, metode itu bekerja sedikit seperti ..." Saya mungkin lakukan jika kita membahas teori daripada dampaknya).

public static bool All<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
    if (source == null)
    {
        throw Error.ArgumentNull("source");
    }
    if (predicate == null)
    {
        throw Error.ArgumentNull("predicate");
    }
    foreach (TSource current in source)
    {
        if (!predicate(current))
        {
            return false;
        }
    }
    return true;
}

Implementasi Anymenurut ILSpy:

public static bool Any<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
    if (source == null)
    {
        throw Error.ArgumentNull("source");
    }
    if (predicate == null)
    {
        throw Error.ArgumentNull("predicate");
    }
    foreach (TSource current in source)
    {
        if (predicate(current))
        {
            return true;
        }
    }
    return false;
}

Tentu saja, mungkin ada beberapa perbedaan halus dalam IL yang dihasilkan. Tapi tidak, tidak ada. IL hampir sama tetapi untuk inversi yang jelas untuk mengembalikan true pada pertandingan predikat versus kembali salah pada predikat mismatch.

Ini hanya linq-untuk-objek saja. Mungkin saja beberapa penyedia linq lain memperlakukan satu jauh lebih baik daripada yang lain, tetapi kemudian jika itu yang terjadi, itu cukup acak mana yang mendapat implementasi yang lebih optimal.

Tampaknya aturan itu hanya berlaku untuk seseorang yang merasa if(determineSomethingTrue)lebih sederhana dan lebih mudah dibaca daripada if(!determineSomethingFalse). Dan dalam keadilan, saya pikir mereka sedikit banyak titik di mana saya sering menemukan if(!someTest)* membingungkan ketika ada tes alternatif dengan verbositas dan kompleksitas yang sama yang akan kembali benar untuk kondisi yang ingin kita tindak lanjuti. Namun sungguh, secara pribadi saya tidak menemukan apa pun untuk mendukung salah satu dari dua alternatif yang Anda berikan, dan mungkin akan sedikit condong ke yang pertama jika predikatnya lebih rumit.

* Tidak membingungkan karena saya tidak mengerti, tetapi membingungkan karena saya khawatir ada beberapa alasan halus untuk keputusan yang saya tidak mengerti, dan butuh beberapa lompatan mental untuk menyadari bahwa "tidak, mereka hanya memutuskan untuk melakukan dengan begitu, tunggu apa lagi yang saya cari dengan kode ini? ... "

Jon Hanna
sumber
8
Saya tidak yakin apa yang dilakukan di belakang garis, tetapi bagi saya jauh lebih mudah dibaca adalah: jika (tidak ada) daripada jika (semua tidak sama).
VikciaR
49
Ada perbedaan besar ketika enumerasi Anda tidak memiliki nilai. 'Any' akan selalu mengembalikan FALSE, dan 'All' selalu mengembalikan TRUE. Jadi mengatakan dari satu adalah padanan logis dari yang lain tidak sepenuhnya benar!
Arnaud
44
@Arnaud Anyakan kembali falsedan karenanya !Anyakan kembali true, jadi mereka identik.
Jon Hanna
11
@Arnaud Tidak ada orang yang berkomentar yang mengatakan bahwa Any dan All secara logis setara. Atau dengan kata lain, semua orang yang berkomentar tidak mengatakan bahwa Any dan All secara logis setara. Kesetaraannya adalah antara! Any (predikat) dan All (! Predicate).
Jim Balter
7
@MacsDickinson sama sekali tidak berbeda, karena Anda tidak membandingkan predikat yang berlawanan. Setara dengan !test.Any(x => x.Key == 3 && x.Value == 1)yang menggunakan Alladalah test.All(x => !(x.Key == 3 && x.Value == 1))(yang memang setara dengan test.All(x => x.Key != 3 || x.Value != 1)).
Jon Hanna
55

Anda mungkin menemukan metode ekstensi ini membuat kode Anda lebih mudah dibaca:

public static bool None<TSource>(this IEnumerable<TSource> source)
{
    return !source.Any();
}

public static bool None<TSource>(this IEnumerable<TSource> source, 
                                 Func<TSource, bool> predicate)
{
    return !source.Any(predicate);
}

Sekarang alih-alih yang asli

if (!acceptedValues.Any(v => v == someValue))
{
    // exception logic
}

Anda bisa mengatakan

if (acceptedValues.None(v => v == someValue))
{
    // exception logic
}
AakashM
sumber
6
Terima kasih - saya sudah berpikir untuk mengimplementasikan ini di perpustakaan umum kami, tapi saya belum memutuskan apakah itu ide yang bagus. Saya setuju bahwa mereka membuat kode lebih mudah dibaca, tetapi saya khawatir mereka tidak menambah nilai yang cukup.
Markus
2
Saya mencari Tidak ada dan tidak menemukannya. Ini jauh lebih mudah dibaca.
Rhyous
Saya harus menambahkan cek nol: return source == null || ! sumber. Setiap (predikat);
Rhyous
27

Keduanya akan memiliki kinerja yang identik karena keduanya menghentikan pencacahan setelah hasilnya dapat ditentukan - Any()pada item pertama yang lulus predikat dievaluasi ke truedan All()pada item pertama predikat dievaluasi false.

Gelas pecah
sumber
21

All sirkuit pendek pada pertandingan pertama yang tidak cocok, jadi itu tidak masalah.

Satu bidang kehalusan adalah itu

 bool allEven = Enumerable.Empty<int>().All(i => i % 2 == 0); 

Adalah benar. Semua item dalam urutan adalah genap.

Untuk lebih lanjut tentang metode ini, lihat dokumentasi untuk Enumerable .

Anthony Pegram
sumber
12
Ya, tapi bool allEven = !Enumerable.Empty<int>().Any(i => i % 2 != 0)itu benar juga.
Jon Hanna
1
@Jon secara semantik tidak ada! = Semua. Jadi secara semantik Anda tidak memiliki apapun atau semua kecuali dalam kasus .All () tidak ada yang hanya merupakan himpunan bagian dari semua koleksi yang mengembalikan nilai true untuk semua dan bahwa perbedaan dapat mengakibatkan bug jika Anda tidak menyadarinya. +1 untuk Anthony
Rune FS
@RuneFS saya tidak mengikuti. Secara semantik dan logis "tidak ada di mana tidak benar bahwa ..." memang sama dengan "semua tempat itu benar bahwa". Misalnya "di mana tidak ada proyek yang diterima dari perusahaan kami?" akan selalu memiliki jawaban yang sama dengan "di mana semua proyek yang diterima dari perusahaan lain?" ...
Jon Hanna
... Sekarang, memang benar bahwa Anda dapat memiliki bug dari asumsi "semua item adalah ..." berarti setidaknya ada satu item yang setidaknya satu item yang memenuhi tes, karena "semua item ... "Selalu benar untuk set kosong, saya tidak membantah sama sekali. Saya menambahkan bahwa masalah yang sama dapat terjadi dengan asumsi "tidak ada item ..." berarti setidaknya satu item tidak memenuhi tes, karena "tidak ada item ..." juga selalu benar untuk set kosong . Bukannya saya tidak setuju dengan poin Anthony, itu yang saya pikir itu juga berlaku untuk yang lain dari dua konstruksi yang sedang dibahas.
Jon Hanna
@Jon Anda berbicara logika dan saya berbicara linguistik. Otak manusia tidak dapat memproses negatif (sebelum memproses positif pada titik mana ia kemudian dapat meniadakannya) sehingga dalam pengertian itu ada perbedaan yang cukup besar di antara keduanya. Itu tidak membuat logika yang Anda usulkan salah
Rune FS
8

All()menentukan apakah semua elemen urutan memenuhi suatu kondisi.
Any()menentukan apakah elemen dari urutan memenuhi syarat.

var numbers = new[]{1,2,3};

numbers.All(n => n % 2 == 0); // returns false
numbers.Any(n => n % 2 == 0); // returns true
emy
sumber
7

Menurut tautan ini

Any - Memeriksa setidaknya satu kecocokan

Semua - Memeriksa apakah semua cocok

rcarvalhoxavier
sumber
1
Anda benar tetapi mereka berhenti pada saat yang sama untuk koleksi yang diberikan. Semua jeda ketika kondisi gagal dan Any Breaks saat itu cocok dengan predikat Anda. Jadi Secara teknis tidak berbeda kecuali secara skenario
WPFKK
6

Karena jawaban lain telah dibahas dengan baik: ini bukan tentang kinerja, ini tentang kejelasan.

Ada banyak dukungan untuk kedua opsi Anda:

if (!acceptedValues.Any(v => v == someValue))
{
    // exception logic
}

if (acceptedValues.All(v => v != someValue))
{
    // exception logic
}

Tetapi saya pikir ini mungkin mencapai dukungan yang lebih luas :

var isValueAccepted = acceptedValues.Any(v => v == someValue);
if (!isValueAccepted)
{
    // exception logic
}

Cukup dengan menghitung boolean (dan menamainya) sebelum meniadakan apa pun, ini sangat jelas dalam pikiran saya.

Michael Haren
sumber
3

Jika Anda melihat sumber Enumerable Anda akan melihat bahwa implementasi Anydan Allcukup dekat:

public static bool Any<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) {
    if (source == null) throw Error.ArgumentNull("source");
    if (predicate == null) throw Error.ArgumentNull("predicate");
    foreach (TSource element in source) {
        if (predicate(element)) return true;
    }
    return false;
}

public static bool All<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) {
    if (source == null) throw Error.ArgumentNull("source");
    if (predicate == null) throw Error.ArgumentNull("predicate");
    foreach (TSource element in source) {
        if (!predicate(element)) return false;
    }
    return true;
}

Tidak ada cara yang satu metode untuk secara signifikan lebih cepat daripada yang lain karena satu-satunya perbedaan terletak pada negasi boolean, jadi lebih suka keterbacaan dibandingkan menang kinerja palsu.

Thomas Ayoub
sumber