Em C#, um caminho é apenas uma string, então nada nele nos diz se ele aponta para um arquivo, para um diretório ou para nada. Quem responde a isso é o sistema de arquivos, e o .NET nos dá três formas de perguntar: File.Exists() e Directory.Exists(), a propriedade Exists de FileInfo e DirectoryInfo, e os bits de atributo por trás de File.GetAttributes().

Para baixar o código-fonte deste artigo, você pode acessar nosso repositório no GitHub.

Path.Exists() responde à pergunta mais curta, “existe alguma coisa aqui?”, com uma única chamada desde o .NET 7. Vamos ver cada uma delas, o que retornam quando o caminho não existe e qual escolher.

Como verificar se um arquivo ou diretório existe em C#?

File.Exists() responde a uma pergunta: este caminho aponta para um arquivo existente? Directory.Exists() responde à mesma pergunta para diretórios. Os dois são métodos estáticos que recebem uma string de caminho e retornam um bool.

Usados juntos, eles determinam se um caminho é um arquivo ou um diretório. File.Exists() retorna false quando o caminho descreve um diretório, e Directory.Exists() retorna false quando ele descreve um arquivo. Assim, para um caminho que existe, exatamente um dos dois é true, a menos que um erro esconda o caminho dos dois.

Nenhum dos dois métodos lança exceção. Segundo o Microsoft Learn, Exists “retorna false se ocorrer algum erro ao tentar determinar se o arquivo especificado existe”, o que abrange um caminho null, um caminho inválido, um disco ausente e um caminho que não temos permissão para ler. Um false significa “não é isso, ou não conseguimos saber”.

A diferenciação entre maiúsculas e minúsculas segue o sistema de arquivos: por padrão, o NTFS não diferencia; os sistemas de arquivos do Linux, sim.

Os dois métodos recebem um caminho, a string que localiza um recurso no sistema de arquivos, e o .NET tem um segundo par de classes que responde às mesmas perguntas a partir de um objeto: FileInfo e DirectoryInfo. Para ver em detalhes como os dois pares diferem, consulte os nossos artigos sobre a comparação entre as classes File e FileInfo em C# e sobre o gerenciamento de diretórios com Directory e DirectoryInfo em C#. Se esses tipos são novidade para você, comece por uma introdução às classes File e Directory desde o básico.

Uso das classes Directory e File

File e Directory são classes estáticas, então não precisamos criar instâncias delas.

As duas classes têm um método Exists(). Vamos criar um arquivo para ver como esses métodos funcionam:

var testFile = Path.Combine(Path.GetTempPath(), "test_file.abc");
File.WriteAllText(testFile, string.Empty);

bool isFile = File.Exists(testFile);
bool isDirectory = Directory.Exists(testFile);

Com File.Exists(), verificamos se o caminho existe como arquivo, e o método retorna true. Também usamos Directory.Exists() para verificar se test_file.abc existe como diretório, e o resultado é false.

Vamos tentar com um diretório:

var testDirectory = Path.Combine(Path.GetTempPath(), "test_directory");
Directory.CreateDirectory(testDirectory);

bool isFile = File.Exists(testDirectory);
bool isDirectory = Directory.Exists(testDirectory);

Desta vez, Directory.Exists() retorna true, enquanto File.Exists() retorna false.

Agora, verificamos as mesmas funções quando não existe nem um diretório nem um arquivo:

var notExistingPath = "someNotExistingPath";

bool isFile = File.Exists(notExistingPath);
bool isDirectory = Directory.Exists(notExistingPath);

Tanto File.Exists() quanto Directory.Exists() retornam false.

Uso das classes DirectoryInfo e FileInfo

FileInfo e DirectoryInfo funcionam de forma parecida com File e Directory, mas não são estáticas. Por isso, precisamos criar uma instância delas, passando o caminho para o construtor.

Quando usamos FileInfo e DirectoryInfo, o acesso ao arquivo acontece apenas uma vez: na primeira leitura de uma propriedade como Exists. Então, se precisarmos fazer muitas operações com o caminho, é mais conveniente usar FileInfo e DirectoryInfo do que File e Directory. Estas últimas acessam o arquivo a cada chamada de método.

De qualquer forma, precisamos ter cuidado ao usar FileInfo e DirectoryInfo, porque elas armazenam as informações em cache. Se criarmos uma instância do objeto FileInfo e lermos Exists antes de criar o arquivo com um método de File, a propriedade Exists retorna false mesmo depois da criação do arquivo. Evitamos isso usando o método Refresh().

Vamos reproduzir o problema:

var path = Path.Combine(Path.GetTempPath(), "test_file3.abc");
if (File.Exists(path)) File.Delete(path);

var fileInfo = new FileInfo(path);
bool firstAnswer = fileInfo.Exists;   // false, and now cached

File.WriteAllText(path, string.Empty);

bool staleAnswer = fileInfo.Exists;   // still false
fileInfo.Refresh();
bool freshAnswer = fileInfo.Exists;   // true

A primeira leitura preenche o cache, então a segunda leitura ainda retorna false, embora o arquivo já exista nesse momento. Exists só informa true depois de Refresh().

The Web API Production Checklist, e-book gratuito em inglês

E-book gratuito

Sua Web API está pronta para produção?

33 itens para verificar antes de implantá-la, com a correção de cada um. Um PDF gratuito de 76 páginas para .NET 10.

O e-book está em inglês.

Baixe o checklist gratuito

PDF gratuito. Um único e-mail para enviá-lo. Cancele a inscrição quando quiser.

FileInfo e DirectoryInfo têm uma propriedade Exists que responde à mesma pergunta que o método Exists() de File e Directory.

Vamos usar FileInfo e DirectoryInfo para verificar a existência de um arquivo:

var testFile = Path.Combine(Path.GetTempPath(), "test_file2.abc");
File.WriteAllText(testFile, string.Empty);

var fileInfo = new FileInfo(testFile);
var directoryInfo = new DirectoryInfo(testFile);

bool isFile = fileInfo.Exists;
bool isDirectory = directoryInfo.Exists;

Ao verificar testFile, recebemos os resultados esperados: fileInfo.Exists retorna true, enquanto directoryInfo.Exists retorna false.

Lidar com diretórios é bem simples:

var testDirectory = Path.Combine(Path.GetTempPath(), "test_directory2");
Directory.CreateDirectory(testDirectory);

var fileInfo = new FileInfo(testDirectory);
var directoryInfo = new DirectoryInfo(testDirectory);

bool isFile = fileInfo.Exists;
bool isDirectory = directoryInfo.Exists;

Com testDirectory, fileInfo.Exists retorna false e directoryInfo.Exists retorna true.

Vejamos o que fazer quando não existe nem um diretório nem um arquivo:

var notExistingPath = "someNotExistingPath2";

var fileInfo = new FileInfo(notExistingPath);
var directoryInfo = new DirectoryInfo(notExistingPath);

bool isFile = fileInfo.Exists;
bool isDirectory = directoryInfo.Exists;

Nesse caso, tanto fileInfo.Exists quanto directoryInfo.Exists retornam false, porque notExistingPath não existe nem como arquivo nem como diretório.

Como verificar se um caminho existe em C#?

Path.Exists() responde à pergunta mais curta: existe alguma coisa neste caminho, seja arquivo ou diretório? O método recebe uma string de caminho anulável, retorna um bool e chegou no .NET 7.

Uma única chamada substitui o par File.Exists(path) || Directory.Exists(path) que os leitores escrevem à mão, e ela consulta o sistema de arquivos uma vez, em vez de até duas.

O Microsoft Learn observa que Path.Exists() retorna true para arquivos não regulares existentes, como pipes, e true para um link cujo destino não existe mais.

O Microsoft Learn desaconselha usar File.Exists() para validar caminhos, e o motivo também vale aqui: um caminho malformado retorna false, a mesma resposta dada por um caminho perfeitamente válido que não aponta para nada. Verificar a string em si é outra tarefa, e ela começa por Path.GetInvalidPathChars().

Nenhuma dessas verificações está livre de condição de corrida: outro processo pode criar ou excluir o caminho entre a nossa verificação e a próxima chamada.

Vamos fazer as três perguntas sobre o mesmo caminho:

var path = Path.Combine(Path.GetTempPath(), "test_directory");

bool anythingHere = Path.Exists(path);
bool isDirectory = Directory.Exists(path);
bool isFile = File.Exists(path);

Path.Exists() é true sempre que qualquer uma das outras duas também for.

Os caracteres que um caminho não pode conter são assunto do nosso artigo sobre a classe Path e seus métodos auxiliares para caracteres inválidos.

Como diferenciar um arquivo de um diretório com FileAttributes?

File.GetAttributes() lê os bits de atributo de um caminho em uma única chamada, e FileAttributes.Directory é o bit que resolve a nossa questão. Ele está definido para um diretório e não está definido para um arquivo, então attributes.HasFlag(FileAttributes.Directory) classifica qualquer coisa que exista e que possamos ler.

O problema está no que acontece quando o caminho não existe. File.GetAttributes() lança uma exceção, e a exceção que recebemos depende de qual parte do caminho está faltando: FileNotFoundException quando só o último segmento não existe, DirectoryNotFoundException quando um diretório acima dele também não existe. As duas derivam de IOException, então capturar o tipo base cobre as duas, e capturar apenas FileNotFoundException deixa uma brecha.

A forma mais limpa é parar de usar exceções para controlar o fluxo. Pergunte primeiro a Path.Exists() e leia os atributos apenas quando a resposta for true. Isso transforma duas rotas de exceção em uma única ramificação, com a mesma quantidade de código.

The Web API Production Checklist, e-book gratuito em inglês

E-book gratuito

Sua Web API está pronta para produção?

33 itens para verificar antes de implantá-la, com a correção de cada um. Um PDF gratuito de 76 páginas para .NET 10.

O e-book está em inglês.

Baixe o checklist gratuito

PDF gratuito. Um único e-mail para enviá-lo. Cancele a inscrição quando quiser.

Vejamos como usar File.GetAttributes():

var testFile = Path.Combine(Path.GetTempPath(), "test_file4.abc");
File.WriteAllText(testFile, string.Empty);

var attributes = File.GetAttributes(testFile);

bool isDirectory = attributes.HasFlag(FileAttributes.Directory);
bool isFile = !isDirectory;

Se o caminho existe, o indicador FileAttributes.Directory nos diz que ele é um diretório. Se esse indicador não estiver definido, com certeza o caminho é um arquivo.

A enumeração FileAttributes também nos diz se o arquivo é um arquivo normal, se está oculto, se é somente leitura, se está compactado e muitas outras coisas. Podemos encontrar todos os detalhes lendo FileAttributes Enum no Microsoft Learn.

Quando o caminho não existe, File.GetAttributes() lança uma exceção, então perguntamos primeiro a Path.Exists():

var path = Path.Combine(Path.GetTempPath(), "someNotExistingPath");

bool isDirectory = false;
bool isFile = false;

if (Path.Exists(path))
{
    var attributes = File.GetAttributes(path);

    isDirectory = attributes.HasFlag(FileAttributes.Directory);
    isFile = !isDirectory;
}

Para um caminho inexistente, os dois indicadores continuam false, e File.GetAttributes() nunca é executado, então não há exceção para capturar.

A Microsoft documenta FileAttributes.Directory como compatível com Windows, Linux e macOS. Vários outros indicadores da enumeração, entre eles Archive e Compressed, não têm suporte nenhum no Linux nem no macOS, então consulte a documentação da enumeração antes de depender de qualquer indicador além deste.

Qual verificação usar para um caminho em C#?

Quando a única pergunta é se existe alguma coisa ali, use Path.Exists(). Ele nunca lança exceção e, no .NET 7 e em versões posteriores, uma única chamada é a resposta correta mais curta.

Para diferenciar um diretório de um arquivo, chame Directory.Exists() e File.Exists(). Juntos, eles classificam qualquer caminho como diretório, arquivo ou nenhum dos dois, e nenhuma das duas chamadas lança exceção.

FileInfo e DirectoryInfo compensam quando vamos fazer mais com o caminho do que apenas perguntar sobre ele. As duas armazenam a resposta em cache na primeira vez que lemos uma propriedade como Exists, então essa resposta pode ficar desatualizada, e Refresh() é o que limpa esse cache. Esse cache é um custo quando queremos responder a uma única pergunta e uma economia quando queremos responder a dez.

Use File.GetAttributes() quando os outros bits de atributo também importam, como oculto e somente leitura, e proteja a chamada, porque ela lança uma exceção quando o caminho não existe.

A tabela coloca as seis chamadas lado a lado, com o que cada uma retorna para um caminho inexistente e para um diretório:

ChamadaPergunta que respondeCaminho inexistenteDiretório como argumentoDisponível desde
File.Exists(path)Este é um arquivo existente?falsefalse.NET Framework 1.0
Directory.Exists(path)Este é um diretório existente?falsetrue.NET Framework 1.0
Path.Exists(path)Existe alguma coisa aqui, arquivo ou diretório?falsetrue.NET 7
new FileInfo(path).ExistsEste é um arquivo existente?falsefalse.NET Framework 1.0
new DirectoryInfo(path).ExistsEste é um diretório existente?falsetrue.NET Framework 1.0
File.GetAttributes(path)Quais bits de atributo estão definidos, incluindo Directory?lança exceçãoretorna atributos com Directory definido.NET Framework 1.0

Todas as chamadas da tabela consultam o sistema de arquivos real, o que torna o código ao redor difícil de cobrir com testes unitários. Mostramos como testar código que acessa o sistema de arquivos sem tocar no disco em um artigo separado.

A figura coloca as duas verificações Exists() em um mesmo eixo, e o ramo âmbar na parte de baixo é o caso em que as duas retornam false:

Fluxo que vai de uma string de caminho até três resultados: diretório, arquivo ou nada existe (ou não conseguimos lê-lo).

Conclusão

Em resumo, mostramos três formas de testar se um caminho é um arquivo ou um diretório em C#. Podemos verificar se um caminho existe como arquivo ou diretório com as classes File, Directory, FileInfo e DirectoryInfo. Depois, vimos outra possibilidade usando FileAttributes. Podemos obter FileAttributes do caminho e verificar se o indicador Directory está definido.

Depois que sabemos o que um caminho é, geralmente surgem duas perguntas: como contar os arquivos dentro dele, quando é um diretório, e se outro processo está com o arquivo aberto, quando é um arquivo.

Testado com .NET 10.0.10.