LINQ – Any() x Contains()

Existem dois tipos de developer: os que apenas querem fazer funcionar e os que querem entender o que estão fazendo para fazer funcionar direito (leia-se bem feito).

Se você é o segundo, provavelmente já se questionou em algum momento quando se usa Any() ou Contains() nas consultas com LINQ. É claro que a resposta é: depende! (hehe). Em grande parte dos casos o resultado será o mesmo e o comportamento semelhante.

Só para recaptular:

Contains() é um método e o seu desempenho depende muito do próprio uso. Em um List tem complexidade O(n), enquanto em um HashSet seria O(1).

Any() é um método de extensão, é mais flexível. Podemos aplicar uma lambda/delegate. Isto teria uma complexidade de O(n).

Vale lembrar que Contains() também possui um método de extensão IEnumerable<T>, que pensando bem, possui uma instância em quase todas coleções.

Se você for fazer algum benchmark, verá que em grande parte das vezes o Any() é um pouco mais lento que Contains() porque não precisa invocar um delegate para cada elemento, ele utiliza a instância Equals. (apenas e se o tipo T não implementa a interface IEquatable<T>) Mas no final das contas se você olhar para a complexidade uma a uma, esta performance será quase a mesma. Agora acho que deu pra entender o porque da resposta “depende”.  =)

Se ainda não deu pra entender, veja bem:

Any()
Determina se qualquer elemento de uma seqüência satisfaz uma condição.
IEnumerable.Any(Extension method)
colecao.Any(i => i == 1);

Contains()
Determina se um elemento está em uma coleção.
IEnumerable.Contains(Object Method)
colecao.Contains(1);

E ainda também temos o Exists(), que não falamos aqui. Ele determina se a List(T) contém elementos que atendam às condições definidas por uma condição.
List.Exists (Object method)

Mas nem tudo são flores.
Usando Contains() com Entity Framework/NHibernate a saída SQL será transcrita para a clausula “WHERE IN”. E aí que mora o perigo, pois isto afeta o desempenho da aplicação, além do problema de alguns bancos como o Oracle que tem o limite de 1000 na clausula IN (ou seja, passou disso você tem 2 problemas).

Para o Entity Framework 6 o problema de performance utilizando Contains foi melhorado, quanto à clausula IN fique atento nos testes para atender o seu cenário e caso seja preciso um Join resolve.

Já passou por algo semelhante? Está usando o EF6? Mande seu feedback. =)
Abraço.

<Post atualizado (em vermelho) com a contribuição do Rodrigo Vidal e Abner das Dores.>

  • rodrigovidal

    Kono-san tudo bem?

    Acho que você se enganou no inicio do post, a complexidade do Contains é O(n) e não O(1) como você afirmou.

  • Hugo Leonardo

    Em se tratando de ORM, tive uma experiência parecida, no NHibernate, entre os métodos Any() e Count().

    Na prática, quando se utiliza uma condição neste métodos:
    Any() – retorna um booleano que indica se algum registro atende a condição informada;
    Count() – retorna a quantidade de registros que atendem à condição informada.

    Pensando a grosso modo, usando o Count() com uma condicional verificando se o resultado é maior que 0 (zero) temos o mesmo efeito que o Any() porém, com mais código implementado. Enfim, não tenho dúvida que um desenvolvedor que apenas “faz funcionar” iria usar o “ambicioso” método Any().

    Mas é aí que encontramos um problema de performance.
    Demonstro abaixo a maneira que o NHibernate interpreta cada método:

    Any(condição) – é gerado um SELECT de todos os campos da tabela:

    SELECT CODIGO, DESCRICAO, CAMPOX, CAMPOY FROM TABELA WHERE

    Count(condição) – é gerado APENAS um SELECT COUNT:

    SELECT COUNT(*) FROM TABELA WHERE

    Portanto, o Any() retorna o(s) registro(s) inteiro(s) internamente, enquanto o Count() retorna apenas um número, ou seja, bem mais “leve”!

    Realmente temos que nos questionar constantemente se o que está sendo proposto, de fato, é a melhor solução!

    Valeu!

  • Fala Vidalzinho… Valeu pelo feedback aqui. Mas no meu ponto de vista a complexidade de Contains é O(1) equiparável ao Any. Um se dispõe ao equals direto e o outro a uma condição (lambda) de cada elemento.

  • rodrigovidal

    http://referencesource.microsoft.com/#mscorlib/system/collections/generic/list.cs#334. Queria entender o seu ponto de vista, de como o Contains poderia ser O(1).

  • Opa… OK. Vamos lá.
    Contains em uma lista é O(n), enquanto em um Hashset ele tem complexidade O(1).
    Any é um método de extensao, que aplica um delegate para cada objeto, sendo assim de complexidade O(n).

    Acho que posso melhorar acima dizendo isto e colocando sua referência. Mas reafirmando, a complexidade do Contains é O(n) apenas em uma lista. Concorda?

    (ps.: acabei de derramar café no teclado do meu laptop. fudeu tudo…)

  • rodrigovidal

    A complexidade do Contains depende da estrutura de dados obviamente. Mas seu exemplo era em cima de lista então ficou incoerente. Você necessariamente tem que dizer de qual estrutura de dados está falando.

  • Completando… Dê uma olhada neste post (antigo…2006) quanto a complexidade para um HashSet. http://blogs.msdn.com/b/bclteam/archive/2006/11/09/introducing-hashset-t-kim-hamilton.aspx

    Mas foi uma boa observação. Vou fazer um update1 logo abaixo do “vale lembrar…”

  • Grande Hugo.
    Valeu o feedback por aqui. Boa visão..
    E exatamente essa discussão o pessoal levou numa thread lá no Facebook (irei te marcar).

    O pessoal tem usado Dapper para melhorar isso.
    Tem alguns links de performance com ORM tb que são bem legais:

    => http://weblogs.asp.net/fbouma/fetch-performance-of-various-net-orm-data-access-frameworks-part-2
    http://mono.servicestack.net/benchmarks/
    http://blog.staticvoid.co.nz/2012/3/24/entity_framework_comparative_performance

  • rodrigovidal

    Kono, em lugar nenhum do post você falou de HashSet O.o. Como eu falei todos os seus exemplos eram em cima de lista então inferi que o primeiro quadro se referia a listas. Não faz sentido nenhum você me dizer que aquele Contains se referia a HashSet sem mencionar isso em lugar algum.

  • Rodrigo Kono

    Exato. Acabei colocando lista no exemplo enfaixou errado com o contexto. Irei alterar para “colecao” para deixar implícito e vou adicionar essas infos.
    Valeu demais pela obs.

  • Exato. Acabei colocando lista no exemplo e acabou ficando errado com o contexto. Irei alterar para “colecao” para deixar implícito e vou adicionar essas infos.
    Valeu demais pela obs.

  • Sim. Vc tem razão Vidal. Percebi isto nesta discussão. Valeu!

  • Completando… Dê uma olhada neste post (antigo…2006) quanto a complexidade para um HashSet. http://blogs.msdn.com/b/bclteam/archive/2006/11/09/introducing-hashset-t-kim-hamilton.aspx

    Mas foi uma boa observação. Vou fazer um update1 logo abaixo do “vale lembrar…”