ToList or not ToList? That is the question

Raphael Castilho

Utilizar os recursos da linguagem de programação e dos frameworks de forma adequada é fundamental para criação de aplicações performáticas. Algumas operações inadequadas podem ser suficientes para sacrificar até mesmo bons algoritmos. Se tratando de performance, um bom exemplo de equívoco em .NET que pode gerar dores de cabeça está no uso abusivo do método ToList.

O método ToList tem como objetivo materializar uma lista de elementos em memória. Na prática, ele retorna uma nova instância de List<TSource> a partir de um IEnumerable<TSource>. Geralmente, essa é uma operação rápida, porém quando trabalhamos com aplicações onde a performance é atributo de qualidade crítico, entender o impacto de sua utilização é fundamental para evitar o uso equivocado e consequentemente colocar a performance da aplicação em risco.

Vamos consultar a implementação do método ToList, no GitHub da Microsoft e entender como realmente funciona a materialização dessa lista:

public static List<TSource> ToList<TSource>(this IEnumerable<TSource> source)
{
    if (source == null)
    {
        ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
    }

    return source is IIListProvider<TSource> listProvider ? 
            listProvider.ToList() : new List<TSource>(source);
}

Perceba alguns aspectos importantes sobre o método ToList observando sua implementação:

  • Com o objetivo de otimizar performance, o método tratará de forma diferente coleções do tipo IIListProvider, executando uma implementação específica de ToList para cada tipo de operação (Select, Where, OrderBy, etc). Para coleções que não são do tipo IIListProvider, uma lista é criada passando o enumerable fonte como parâmetro. Independente do tipo da coleção, uma nova instância de List<TSource> é criada e os objetos são copiados para essa nova lista, gerando pressão sobre a memória;
  • Caso o tamanho da lista ultrapasse 85.000 bytes, o impacto negativo é agravado, pois a mesma será alocada na Large Object Heap (LOH), tornando o processo de desalocação de memória mais custoso;
  • Um ponto especial em relação a operações como Select e Where, é que ao chamar o método ToList interno, uma nova instância de List<TSource> é criada sem informar uma capacidade (parâmetro capacity), resultando em um array de 4 posições. Considerando que não é possível redimensionar arrays em .NET, teremos uma pressão sobre o garbage collector, pois toda vez que o limite do array for alcançado, um novo array será criado com o dobro da capacidade do anterior, todos elementos serão copiados para o novo array e o array anterior será descartado.

Meça sempre

Para sabermos o quanto a utilização desse recurso impacta a performance, faremos a avaliação de uma operação que utiliza ToList e outra que não utiliza a partir de um enumerable já criado. Para isso, utilizaremos a versão .NET Core 3.1.14 e a ferramenta BenchmarkDotNet.

Métodos utilizados na comparação:

[Benchmark]
public void UsingToList()
{
    var products = _products
        .Where(x => x.Type == ProductType.Electronic)
        .ToList();

    foreach (var product in products)
    {
        Console.WriteLine("Product name is: {0}.", product.Name);
    }
}

[Benchmark]
public void NotUsingToList()
{
    var products = _products
        .Where(x => x.Type == ProductType.Electronic);

    foreach (var product in products)
    {
        Console.WriteLine("Product name is: {0}.", product.Name);
    }
}

Resultado da comparação com uma coleção de 10.000 objetos e posteriormente uma coleção de 50.000 objetos:



O tempo de processamento não aponta uma diferença significativa nesse cenário, contudo a diferença na necessidade de memória é expressiva. Fica claro o impacto negativo ao se utilizar o método ToList, variando de acordo com a quantidade de objetos pertencentes à coleção e seus tamanhos. É importante destacar que em operações que demandam alta performance ou em infraestrutura com recursos limitados, essa alocação excessiva de memória pode ocasionar em uma OutOfMemoryException, interrompendo a execução da aplicação.

Cuidado com ToList em conjunto de ORM’s

Considerando a análise realizada do método ToList, cuidado com sua utilização em conjunto de ORM’s. Observe o código abaixo:

public IEnumerable<Product> GetByType(ProductType type)
{
    return _dbContext
        .Products
        .ToList()
        .Where(product => product.Type == type);
}

Perceba que neste exemplo estamos materializando todos os objetos em memória, para depois executar um filtro nesta lista materializada. Além do consumo de memória, estamos falando de uma consulta mais pesada no banco de dados e uma pressão maior na rede, já que todos os registros desta tabela serão trafegados para a aplicação. Evite materializar as listas de maneira desnecessária em memória.

Mas então, quando utilizar ToList?

Ao consultarmos a documentação da Microsoft, nos deparamos com a seguinte observação:

A utilização do método ToList pode ser adequada para cenários onde o resultado de um Enumerable será utilizado mais de uma vez, fazendo com que a query evaluation não seja executada múltiplas vezes. Neste caso, o valor retornado de ToList serve como um cache para os resultados da query, evitando processamento desnecessário. Ainda assim é importante observar que em situações onde a query retorna uma grande quantidade de objetos, o uso do ToList pode impactar em uso excessivo de memória, valendo avaliar o trade-off em cada situação de uso.

É essencial conhecer os recursos disponíveis na linguagem de programação, nos frameworks e o impacto de cada escolha dentro do nosso código. Uma simples escolha ingênua pode causar uma enorme dor de cabeça. A documentação da Microsoft, o código de referência do framework e livros como C# in depth e Writing High-Performance .NET Code se tornam obrigatórios quando pensamos em criar aplicações eficientes.

Compartilhe este insight:

Comentários

Participe deixando seu comentário sobre este artigo a seguir:

Subscribe
Notify of
guest
7 Comentários
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Anderson
Anderson
1 ano atrás

Parabéns, ótimo conteúdo!

Patrick Segantine
Patrick Segantine
1 ano atrás

Muito bom!!! Não havia lido algo que me chamasse tanto a atenção para este fato.

Lucas
Lucas
1 ano atrás

muito bom

Renan
Renan
1 ano atrás

muito bom! otima explicacao.

Luiz Antônio
Luiz Antônio
1 ano atrás

Boa Castilho! Ótimo conteúdo.

Franklin Hermes
Franklin Hermes
1 ano atrás

Excelente Raphael! como me inscrevo para receber insights em meu e-mail?

Danilo
Danilo
11 meses atrás

Excelente explicação. Obrigado.

AUTOR

Raphael Castilho
Desenvolvedor especialista em .NET com experiência em aplicações corporativas de larga escala

SOLUÇÕES EXIMIACO

Desenvolvimento de Software

Gestão de times para acelerar entregas e atuar na resolução de problemas complexos.

NOVOS HORIZONTES PARA O SEU NEGÓCIO

Nosso time está preparado para superar junto com você grandes desafios tecnológicos.

Entre em contato e vamos juntos utilizar a tecnologia do jeito certo para gerar mais resultados.

Insights EximiaCo

Confira os conteúdos de negócios e tecnologia desenvolvidos pelos nossos consultores:

Engenharia de Software

Três vantagens reais de utilizar orquestradores BPM para serviços

Arquiteto de software e solução com larga experiência corporativa
Desenvolvimento de Software

Os principais desafios ao adotar testes

Especialista em Testes e Arquitetura de Software
Arquitetura de Dados

Insights de um DBA na análise de um plano de execução

Especialista em performance de Bancos de Dados de larga escala

Acesse nossos canais

Simplificamos, potencializamos e aceleramos resultados usando a tecnologia do jeito certo

EximiaCo 2022 – Todos os direitos reservados

7
0
Queremos saber a sua opinião, deixe seu comentáriox
()
x

ToList or not ToList? That is the question

Para se candidatar nesta turma aberta, preencha o formulário a seguir:

Condição especial de pré-venda: R$ 14.000,00 - contratando a mentoria até até 31/01/2023 e R$ 15.000,00 - contratando a mentoria a partir de 01/02/2023, em até 12x com taxas.

Tenho interesse nessa capacitação

Para solicitar mais informações sobre essa capacitação para a sua empresa, preencha o formulário a seguir:

Tenho interesse em conversar

Se você está querendo gerar resultados através da tecnologia, preencha este formulário que um de nossos consultores entrará em contato com você:

O seu insight foi excluído com sucesso!

O seu insight foi excluído e não está mais disponível.

O seu insight foi salvo com sucesso!

Ele está na fila de espera, aguardando ser revisado para ter sua publicação programada.

Tenho interesse em conversar

Se você está querendo gerar resultados através da tecnologia, preencha este formulário que um de nossos consultores entrará em contato com você:

Tenho interesse nessa solução

Se você está procurando este tipo de solução para o seu negócio, preencha este formulário que um de nossos consultores entrará em contato com você:

Tenho interesse neste serviço

Se você está procurando este tipo de solução para o seu negócio, preencha este formulário que um de nossos consultores entrará em contato com você:

× Precisa de ajuda?