André Alves de Lima

Talking about Software Development and more…

Qual a diferença entre EnumerateFiles e GetFiles?

Esses dias atrás eu estava utilizando a classe Directory, mais especificamente os métodos EnumerateFiles e GetFiles, quando eu parei e pensei: “Opa, parece que esses dois métodos produzem o mesmo resultado. Qual seria, então, a diferença entre eles? “. E é justamente isso que eu vou explicar para vocês no post de hoje.

Se olharmos atentamente a documentação, encontramos a explicação sobre a diferença entre esses dois métodos:

The EnumerateFiles and GetFiles methods differ as follows: When you use EnumerateFiles, you can start enumerating the collection of names before the whole collection is returned; when you use GetFiles, you must wait for the whole array of names to be returned before you can access the array. Therefore, when you are working with many files and directories, EnumerateFiles can be more efficient.

Em outras palavras, ao utilizarmos o GetFiles, a aplicação ficará congelada até que todos os arquivos que correspondam ao padrão especificado sejam retornados. Ou seja, no final da chamada do método GetFiles, nós já teremos em mãos o resultado completo da busca. Já a chamada de EnumerateFiles não faz nada até que comecemos a “andar” pelos arquivos. A Microsoft diz na documentação que, se estivermos trabalhando com muitos arquivos e diretórios, o EnumerateFiles pode ser mais eficiente. Então, vamos fazer uns testes para comprovar.

Antes dos testes, uma observação

Antes mesmo de começarmos os testes, quero fazer uma observação. Tanto o método EnumerateFiles quanto o método GetFiles, não são muito resilientes. Ou seja, quando esses métodos estão fazendo o trabalho deles, se uma exceção é lançada, eles simplesmente param no meio do processamento!

Additional information: Access to the path ‘xxx’ is denied.

Certamente, esse não é o resultado que queremos ao utilizarmos esses métodos para varrer uma unidade inteira (ou pastas incluindo subpastas). Portanto, depois de pesquisar um pouco sobre esse problema, encontrei duas metodologias para que o processamento não seja interrompido caso não tenhamos permissão para acessar algum diretório.

Primeiro, nesta discussão no StackOverflow, eu encontrei uma maneira de utilizarmos o método EnumerateFiles sem que ele pare de processar quando encontrar algum diretório sem acesso:

        private static IEnumerable<string> EnumerateFilesSeguro(string path, string searchPattern, System.IO.SearchOption searchOpt)
        {
            try
            {
                var dirFiles = Enumerable.Empty<string>();
                if (searchOpt == System.IO.SearchOption.AllDirectories)
                {
                    dirFiles = System.IO.Directory.EnumerateDirectories(path)
                                        .SelectMany(x => EnumerateFilesSeguro(x, searchPattern, searchOpt));
                }
                return dirFiles.Concat(System.IO.Directory.EnumerateFiles(path, searchPattern));
            }
            catch (UnauthorizedAccessException ex)
            {
                return Enumerable.Empty<string>();
            }
        }

Depois, encontrei nesta outra discussão no StackOverflow o mesmo ajuste para o método GetFiles:

        private static List<string> GetFilesSeguro(string path, string pattern)
        {
            var files = new List<string>();

            try
            {
                files.AddRange(System.IO.Directory.GetFiles(path, pattern, System.IO.SearchOption.TopDirectoryOnly));
                foreach (var directory in System.IO.Directory.GetDirectories(path))
                    files.AddRange(GetFilesSeguro(directory, pattern));
            }
            catch (UnauthorizedAccessException) { }

            return files;
        }

Iremos utilizar esses dois métodos para fazermos os testes de performance de EnumerateFiles e GetFiles.

Testes de performance EnumerateFiles x GetFiles

Pensei em alguns testes básicos para analisar a performance dos dois métodos:

1. Retornar todos os arquivos do drive “C:
2. Pesquisar pelo arquivo “img1.png” no resultado retornado
3. Descobrir qual foi o último arquivo retornado
4. Fazer uma pesquisa direta pelo arquivo “img1.png

Veja como ficou o código desses testes:

        static void Main(string[] args)
        {
            System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();

            // GetFiles:
            try
            {
                sw.Start();
                var getFiles = GetFilesSeguro("C:\\", "*.*");                
                sw.Stop();
                Console.WriteLine("GetFiles: {0}", sw.Elapsed.ToString());

                sw.Reset();
                sw.Start();
                foreach (var file in getFiles)
                    if (file.Contains("img1.png"))
                        break;
                Console.WriteLine("GetFiles pesquisa: {0}", sw.Elapsed.ToString());
                sw.Stop();

                sw.Reset();
                sw.Start();
                var ultimoArquivo = getFiles.Last();
                Console.WriteLine("GetFiles último arquivo: {0}", sw.Elapsed.ToString());
                sw.Stop();

                sw.Reset();
                sw.Start();
                getFiles = GetFilesSeguro("C:\\", "img1.png");
                var arquivo = getFiles.First();
                Console.WriteLine("GetFiles pesquisa direta: {0}", sw.Elapsed.ToString());
                sw.Stop();
            }
            catch { }

            Console.WriteLine("//---------------------------------------//");
            Console.WriteLine("//---------------------------------------//");

            // EnumerateFiles:
            try
            {
                sw.Reset();
                sw.Start();
                var enumerateFiles = EnumerateFilesSeguro("C:\\", "*.*", System.IO.SearchOption.AllDirectories);
                sw.Stop();
                Console.WriteLine("EnumerateFiles: {0}", sw.Elapsed.ToString());

                sw.Reset();
                sw.Start();
                foreach (var file in enumerateFiles)
                    if (file.Contains("img1.png"))
                        break;
                Console.WriteLine("EnumerateFiles pesquisa: {0}", sw.Elapsed.ToString());
                sw.Stop();

                sw.Reset();
                sw.Start();
                var ultimoArquivo = enumerateFiles.Last();
                Console.WriteLine("EnumerateFiles último arquivo: {0}", sw.Elapsed.ToString());
                sw.Stop();

                sw.Reset();
                sw.Start();
                enumerateFiles = EnumerateFilesSeguro("C:\\", "img1.png", System.IO.SearchOption.AllDirectories);
                var arquivo = enumerateFiles.First();
                Console.WriteLine("EnumerateFiles pesquisa direta: {0}", sw.Elapsed.ToString());
                sw.Stop();
            }
            catch { }

            Console.WriteLine("Fim do teste!");
            Console.ReadLine();
        }

E quais foram os resultados? Veja só na screenshot abaixo:

Note que, no primeiro teste, o GetFiles só retorna o controle à aplicação após ter listado todos os arquivos do drive (37,82 segundos). Já o EnumerateFiles, retorna imediatamente o controle à aplicação, uma vez que ele não faz nada até que “andemos” pelo IEnumerable que foi retornado.

Com isso, uma vez que com o GetFiles os dados já estão retornados no array, a pesquisa pelo arquivo “img1.png” é instantânea (129 milisegundos), enquanto que a pesquisa pelo EnumerateFiles levou um pouco mais, já que ele efetivamente só foi listando os arquivos a cada passo do foreach (11 segundos).

O mesmo tipo de diferença temos ao acessar o último arquivo retornado. O resultado do array retornado pelo GetFiles é instantâneo (uma vez que os dados já estão todos retornados e armazenados no array). Enquanto isso, para retornarmos o último arquivo pelo método EnumerateFiles, o total de tempo foi de 35,75 segundos.

Finalmente, o último teste foi pela pesquisa direta à primeira ocorrência do arquivo “img1.png” no disco “C: “. Nesse caso, ambos os métodos demoraram mais do que poucos segundos, porém o método EnumerateFiles executou mais rápido do que o GetFiles (10,67 segundos contra 27,66 segundos). Isso porque, no caso do EnumerateFiles, nós podemos parar de procurar o arquivo uma vez que encontramos a primeira ocorrência. Já com o GetFiles, todos os arquivos do drive “C: ” serão obrigatoriamente pesquisados.

Minhas conclusões

Levando em conta o pior cenário possível para o EnumerateFiles (listar todos os arquivos do disco), mesmo assim ele acabou tendo uma melhor performance (35,75 segundos contra 37,82 segundos do GetFiles). Dessa forma, a meu ver, acredito que seja mais “seguro” usarmos o EnumerateFiles (em termos de performance). Provavelmente deve existir algum cenário em que o GetFiles seja mais recomendado (por exemplo, quando temos que iterar pelos arquivos mais de uma vez), mas, acredito que na grande maioria dos casos, ou o EnumerateFiles executará mais rápido ou, na pior das hipóteses, executará no mesmo tempo que o GetFiles.

Espero que vocês tenham gostado dessa análise dos métodos EnumerateFiles e GetFiles. O importante mesmo é sabermos a diferença entre os dois para que, da próxima vez que tivermos que listar arquivos do disco, tenhamos base para decidirmos qual dos dois utilizar.

E você, está listando os arquivos do disco na sua aplicação? Se sim, deixe um comentário nos falando qual dos dois métodos você está utilizando e se existe um motivo de você ter utilizado um dos métodos especificamente.

Por fim, convido você a inscrever-se na minha newsletter. Ao fazer isso, você receberá um e-mail toda semana sobre o artigo publicado e ficará sabendo também em primeira mão sobre o artigo da próxima semana, além de receber dicas “bônus” que eu só compartilho por e-mail. Inscreva-se aqui ou utilizando o formulário logo abaixo.

Até a próxima!

André Lima

Newsletter do André Lima

* indicates required



Powered by MailChimp

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *