LINQ: nem todos vs todos não

272

Muitas vezes, quero verificar se um valor fornecido corresponde a um em uma lista (por exemplo, ao validar):

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

Recentemente, notei o ReSharper me pedindo para simplificar essas consultas para:

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

Obviamente, isso é logicamente idêntico, talvez um pouco mais legível (se você fez muita matemática), minha pergunta é: isso resulta em um desempenho atingido?

Parece que deveria (ou seja, .Any()soa como um curto-circuito, enquanto .All()parece que não), mas não tenho nada para substanciar isso. Alguém tem um conhecimento mais profundo sobre se as consultas resolverão o mesmo ou se o ReSharper está me desviando?

Marca
fonte
6
Você já tentou desmontar o código Linq para ver o que está fazendo?
RQDQ
9
Neste caso, eu seria realmente ir com if (acceptedValues.Contains (someValue)!), Mas é claro que isso não era a questão :)
csgero
2
@csgero eu concordo. O acima foi uma simplificação (talvez super simplificação) da lógica real.
Mark
1
"Parece que deveria (ou seja, qualquer () soa como um curto-circuito, enquanto .All () parece que não)" - Não para quem tem intuições sonoras. A equivalência lógica que você nota implica que eles são igualmente curtos-circuitos. Um momento de reflexão revela que Tudo pode sair assim que um caso não qualificado é encontrado.
Jim Balter
3
Não concordo universalmente com o ReSharper sobre isso. Escreva linhas de pensamento sensatas. Se você quiser lançar uma exceção se um item necessário está faltando: if (!sequence.Any(v => v == true)). Se você quiser continuar somente se tudo está em conformidade com uma determinada especificação: if (sequence.All(v => v < 10)).
Timo

Respostas:

344

Implementação de Allacordo com o ILSpy (como na verdade eu fui e olhei, em vez do "bem, esse método funciona um pouco como ..." eu poderia fazer se estivéssemos discutindo a teoria e não o impacto).

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;
}

Implementação de Anyacordo com 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;
}

Obviamente, pode haver alguma diferença sutil na IL produzida. Mas não, não, não há. O IL é praticamente o mesmo, mas pela inversão óbvia de retornar true na correspondência de predicado versus retornar falso na incompatibilidade de predicado.

Isso é linq-for-objects, é claro. É possível que algum outro provedor de linq trate um muito melhor que o outro, mas se esse for o caso, é bem aleatório qual deles obteve a implementação mais ideal.

Parece que a regra se resume apenas a alguém que se sente if(determineSomethingTrue)mais simples e mais legível do que if(!determineSomethingFalse). E, para ser sincero, acho que eles têm um certo ponto em que muitas vezes acho if(!someTest)confuso * quando há um teste alternativo de igual verbosidade e complexidade que retornaria verdadeiro para a condição em que queremos agir. No entanto, realmente, pessoalmente, não encontro nada a favor de uma das outras alternativas que você oferece, e talvez se inclinasse levemente em relação à primeira se o predicado fosse mais complicado.

* Não é confuso como em Eu não entendo, mas confuso como em Eu me preocupo que haja alguma razão sutil para a decisão que eu não entendo, e são necessários alguns saltos mentais para perceber que "não, eles apenas decidiram fazer dessa forma, espere o que eu estava olhando para este pedaço de código novamente? ... "

Jon Hanna
fonte
8
Não tenho certeza do que é feito por trás das linhas, mas para mim é muito mais legível: se (não houver) do que se (nem todos são iguais).
VikciaR
49
Há uma grande diferença quando sua enumeração não tem valores. 'Qualquer' sempre retornará FALSO, e 'Todos' sempre retornará VERDADEIRO. Dizer que um é o equivalente lógico do outro não é inteiramente verdade!
Arnaud
44
O @Arnaud Anyretornará falsee, portanto !Any, retornará true, portanto são idênticos.
Jon Hanna
11
@ Arnaud Não há nenhuma pessoa que comentou que disse que Qualquer e Tudo são logicamente equivalentes. Ou, dito de outra maneira, todas as pessoas que comentaram não disseram que Qualquer e Tudo são logicamente equivalentes. A equivalência é entre! Qualquer (predicado) e Todos (! Predicado).
Jim Balter
7
@MacsDickinson não faz diferença, porque você não está comparando predicados opostos. O equivalente a !test.Any(x => x.Key == 3 && x.Value == 1)esse uso Allé test.All(x => !(x.Key == 3 && x.Value == 1))(que é realmente equivalente a test.All(x => x.Key != 3 || x.Value != 1)).
Jon Hanna
55

Você pode encontrar esses métodos de extensão para tornar seu código mais legível:

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

Agora, em vez do seu original

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

você poderia dizer

if (acceptedValues.None(v => v == someValue))
{
    // exception logic
}
AakashM
fonte
6
Obrigado - eu já estava pensando em implementá-los em nossa biblioteca de bens comuns, mas ainda não decidi se é uma boa ideia. Concordo que eles tornam o código mais legível, mas estou preocupado que eles não adicionem valor suficiente.
Mark
2
Procurei por None e não o encontrei. É muito mais legível.
Rhyous
Eu tive que adicionar verificações nulas: fonte de retorno == null || ! source.Any (predicado);
Rhyous
27

Ambos teriam desempenho idêntico porque ambos param a enumeração após a determinação do resultado - Any()no primeiro item que o predicado passado avalia truee All()no primeiro item que o predicado avalia false.

Vidro quebrado
fonte
21

All curto-circuitos no primeiro non-match, por isso não é um problema.

Uma área de sutileza é que

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

É verdade. Todos os itens da sequência são pares.

Para saber mais sobre esse método, consulte a documentação de Enumerable.All .

Anthony Pegram
fonte
12
Sim, mas também bool allEven = !Enumerable.Empty<int>().Any(i => i % 2 != 0)é verdade.
Jon Hanna
1
@ Jon semanticamente nenhum! = Todos. Portanto, semanticamente, você tem nenhum ou todos, exceto no caso de .All () none é apenas um subconjunto de todas as coleções que retornam verdadeiras para todos e essa discrepância pode resultar em bugs se você não souber. +1 para esse Anthony
Rune FS
@RuneFS Eu não sigo. Semanticamente e logicamente "nenhum onde é falso que ..." é realmente o mesmo que "tudo onde é verdade isso". Por exemplo, "onde nenhum dos projetos aceitos da nossa empresa?" sempre terá a mesma resposta que "onde todos os projetos aceitos de outras empresas?" ... ...
Jon Hanna
... Agora, é verdade que você pode ter erros assumindo "todos os itens são ..." significa que há pelo menos um item que é pelo menos um item que realiza o teste, já que "todos os itens ... "é sempre verdade para o conjunto vazio, não discuto isso. No entanto, acrescentei que o mesmo problema pode ocorrer ao assumir que "nenhum dos itens ..." significa que pelo menos um item não cumpre o teste, já que "nenhum dos itens ..." também é sempre verdadeiro para o conjunto vazio . Não é que eu não concorde com o ponto de Anthony, mas acho que isso também vale para o outro dos dois conceitos em discussão.
Jon Hanna
@ Jon, você está falando de lógica e eu estou falando de linguística. O cérebro humano não pode processar um negativo (antes de processar o positivo, nesse ponto, pode negá-lo); nesse sentido, há uma grande diferença entre os dois. Isso não faz a lógica que você propor incorreta
Rune FS
8

All()determina se todos os elementos de uma sequência satisfazem uma condição.
Any()determina se algum elemento de uma sequência satisfaz a condição.

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

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

De acordo com este link

Qualquer - verifica se há pelo menos uma correspondência

Tudo - verifica se todos correspondem

rcarvalhoxavier
fonte
1
você está certo, mas eles param ao mesmo tempo para uma determinada coleção. Todas as interrupções quando a condição falhar e Qualquer quebra quando corresponder ao seu predicado. Então, tecnicamente, não é diferente, exceto cenaticamente
WPFKK
6

Como outras respostas foram bem abordadas: não se trata de desempenho, é de clareza.

Há amplo suporte para as duas opções:

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

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

Mas acho que isso pode obter um suporte mais amplo :

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

Simplesmente calcular o booleano (e nomeá-lo) antes de negar qualquer coisa esclarece muito isso.

Michael Haren
fonte
3

Se você der uma olhada na fonte Enumerable, verá que a implementação de Anye Allé bem próxima:

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;
}

Não há como um método ser significativamente mais rápido que o outro, pois a única diferença está em uma negação booleana; portanto, prefira a legibilidade do que a falsa vitória no desempenho.

Thomas Ayoub
fonte