En C#, una ruta no es más que una cadena, así que nada en ella nos dice si corresponde a un archivo, a un directorio o a nada en absoluto. Eso lo tiene que responder el sistema de archivos, y .NET nos da tres formas de preguntárselo: File.Exists() y Directory.Exists(), la propiedad Exists de FileInfo y DirectoryInfo, y los bits de atributos que hay detrás de File.GetAttributes().

Para descargar el código fuente de este artículo, puedes visitar nuestro repositorio de GitHub.

Path.Exists() responde a la pregunta más corta, “¿hay algo aquí?”, con una sola llamada desde .NET 7. Vamos a ver cada una de estas formas, qué devuelven cuando la ruta no existe y cuál conviene usar.

¿Cómo comprobar si existe un archivo o un directorio en C#?

File.Exists() responde a una sola pregunta: ¿esta ruta corresponde a un archivo existente? Directory.Exists() responde a la pregunta equivalente para los directorios. Ambos son métodos estáticos que reciben una cadena con la ruta y devuelven un bool.

Usados juntos, determinan si una ruta es un archivo o un directorio. File.Exists() devuelve false cuando la ruta corresponde a un directorio, y Directory.Exists() devuelve false cuando corresponde a un archivo. Así que, para una ruta que existe, exactamente uno de los dos es true, salvo que un error se la oculte a los dos.

Ninguno de los dos métodos lanza excepciones. Según Microsoft Learn, Exists “devuelve false si se produce cualquier error al intentar determinar si existe el archivo especificado”, lo que incluye una ruta null, una ruta no válida, un disco que falta y una ruta que no tenemos permiso para leer. Un false significa “no es eso, o no hemos podido saberlo”.

La distinción entre mayúsculas y minúsculas depende del sistema de archivos: NTFS, de forma predeterminada, no distingue mayúsculas de minúsculas, y los sistemas de archivos de Linux sí.

Ambos métodos reciben una ruta, la cadena que localiza un recurso en el sistema de archivos, y .NET tiene un segundo par de clases que responden a las mismas preguntas a partir de un objeto: FileInfo y DirectoryInfo. Para ver en detalle en qué se diferencian los dos pares, consulta nuestros artículos sobre la comparación de las clases File y FileInfo en C# y sobre cómo gestionar directorios con Directory y DirectoryInfo en C#. Si estos tipos son nuevos para ti, empieza por las clases File y Directory desde cero.

Usar las clases Directory y File

File y Directory son clases estáticas, así que no necesitamos crear instancias de ellas.

Ambas clases tienen un método Exists(). Vamos a crear un archivo para ver cómo funcionan estos métodos:

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

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

Con File.Exists(), comprobamos si la ruta existe como archivo, y el método devuelve true. También usamos Directory.Exists() para comprobar si test_file.abc existe como directorio, y el resultado es false.

Probemos con un directorio:

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

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

Esta vez Directory.Exists() devuelve true, mientras que File.Exists() devuelve false.

Ahora probamos las mismas funciones cuando no existe ni un directorio ni un archivo:

var notExistingPath = "someNotExistingPath";

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

Tanto File.Exists() como Directory.Exists() devuelven false.

Usar las clases DirectoryInfo y FileInfo

FileInfo y DirectoryInfo funcionan de forma parecida a File y Directory, pero no son estáticas. Por eso, tenemos que crear una instancia de ellas y pasar la ruta al constructor.

Cuando usamos FileInfo y DirectoryInfo, el acceso al archivo se hace solo una vez, la primera vez que leemos una propiedad como Exists. Así que, si necesitamos hacer muchas operaciones con rutas, es más conveniente usar FileInfo y DirectoryInfo que File y Directory. Estas últimas acceden al archivo en cada llamada a un método.

De todos modos, debemos tener cuidado al usar FileInfo y DirectoryInfo, porque almacenan información en caché. Si creamos una instancia del objeto FileInfo y leemos Exists antes de crear el archivo con un método de File, la propiedad Exists devuelve false incluso después de crearlo. Lo evitamos con el método Refresh().

Vamos a reproducirlo:

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

La primera lectura llena la caché, así que la segunda sigue devolviendo false aunque para entonces el archivo ya existe. Exists indica true solo después de Refresh().

The Web API Production Checklist, ebook gratuito en inglés

Ebook gratis

¿Tu Web API está lista para producción?

33 puntos que comprobar antes de desplegarla, con la solución de cada uno. Un PDF gratuito de 76 páginas para .NET 10.

El ebook está en inglés.

Descarga la checklist gratis

PDF gratuito. Un solo correo para enviártelo. Puedes darte de baja cuando quieras.

FileInfo y DirectoryInfo tienen una propiedad Exists que responde a la misma pregunta que el método Exists() de File y Directory.

Usemos FileInfo y DirectoryInfo para comprobar si existe un archivo:

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;

Al comprobar testFile obtenemos los resultados esperados: fileInfo.Exists devuelve true, mientras que directoryInfo.Exists devuelve false.

Con los directorios es bastante sencillo:

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;

Con testDirectory, fileInfo.Exists devuelve false y directoryInfo.Exists devuelve true.

Veamos qué hacer cuando no existe ni un directorio ni un archivo:

var notExistingPath = "someNotExistingPath2";

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

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

En este caso, tanto fileInfo.Exists como directoryInfo.Exists devuelven false, porque notExistingPath no existe ni como archivo ni como directorio.

¿Cómo comprobar si existe una ruta en C#?

Path.Exists() responde a la pregunta más corta: ¿hay algo en esta ruta, sea archivo o directorio? Recibe una cadena de ruta anulable, devuelve un bool y llegó con .NET 7.

Una sola llamada sustituye al par File.Exists(path) || Directory.Exists(path) que los lectores escriben a mano, y consulta al sistema de archivos una vez en lugar de hasta dos.

Microsoft Learn indica que Path.Exists() devuelve true para archivos existentes que no son regulares, como las canalizaciones (pipes), y true para un enlace cuyo destino ya no existe.

Microsoft Learn desaconseja usar File.Exists() para validar rutas, y el motivo también se aplica aquí: una ruta mal formada devuelve false, la misma respuesta que da una ruta perfectamente válida que no apunta a nada. Comprobar la propia cadena es otra tarea, y Path.GetInvalidPathChars() es el punto de partida.

Ninguna de estas comprobaciones está libre de condiciones de carrera: otro proceso puede crear o eliminar la ruta entre nuestra comprobación y nuestra siguiente llamada.

Hagamos las tres preguntas sobre la misma ruta:

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() es true siempre que lo sea cualquiera de los otros dos.

Nuestro artículo sobre la clase Path y sus métodos auxiliares para caracteres no válidos explica qué caracteres no puede contener una ruta.

¿Cómo distinguir un archivo de un directorio con FileAttributes?

File.GetAttributes() lee los bits de atributos de una ruta con una sola llamada, y FileAttributes.Directory es el bit que resuelve nuestra pregunta. Está activado para un directorio y desactivado para un archivo, así que attributes.HasFlag(FileAttributes.Directory) clasifica cualquier cosa que exista y que podamos leer.

El problema es qué ocurre cuando la ruta no existe. File.GetAttributes() lanza una excepción, y cuál obtenemos depende de qué parte de la ruta falta: FileNotFoundException cuando solo falta el último segmento, y DirectoryNotFoundException cuando también falta un directorio superior. Ambas derivan de IOException, así que capturar el tipo base cubre las dos, y capturar solo FileNotFoundException deja un hueco.

La forma más limpia es dejar de usar excepciones para controlar el flujo. Pregunta primero a Path.Exists() y lee los atributos solo cuando la respuesta sea true. Así, dos caminos de excepción se convierten en una sola rama, con la misma cantidad de código.

The Web API Production Checklist, ebook gratuito en inglés

Ebook gratis

¿Tu Web API está lista para producción?

33 puntos que comprobar antes de desplegarla, con la solución de cada uno. Un PDF gratuito de 76 páginas para .NET 10.

El ebook está en inglés.

Descarga la checklist gratis

PDF gratuito. Un solo correo para enviártelo. Puedes darte de baja cuando quieras.

Veamos cómo podemos 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;

Si la ruta existe, el indicador FileAttributes.Directory nos dice que la ruta es un directorio. Si este indicador no está activado, la ruta es sin duda un archivo.

La enumeración FileAttributes también nos dice si el archivo es un archivo normal, si está oculto, si es de solo lectura, si está comprimido y muchas otras cosas. Podemos encontrar todos los detalles en la página FileAttributes Enum de Microsoft Learn.

Cuando la ruta no existe, File.GetAttributes() lanza una excepción, así que preguntamos primero 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 una ruta que no existe, ambos indicadores se quedan en false y File.GetAttributes() nunca se ejecuta, así que no hay ninguna excepción que capturar.

Microsoft documenta FileAttributes.Directory como compatible con Windows, Linux y macOS. Otros indicadores de la enumeración, entre ellos Archive y Compressed, no son compatibles en absoluto con Linux ni con macOS, así que consulta la documentación de la enumeración antes de depender de un indicador distinto de este.

¿Qué comprobación conviene usar para una ruta en C#?

Cuando la única pregunta sea si hay algo, usa Path.Exists(). Nunca lanza excepciones, y en .NET 7 y versiones posteriores una sola llamada es la respuesta correcta más corta.

Para distinguir un directorio de un archivo, llama a Directory.Exists() y a File.Exists(). Entre los dos clasifican cualquier ruta como directorio, archivo o ninguna de las dos cosas, y ninguno de los dos lanza excepciones.

FileInfo y DirectoryInfo compensan cuando vamos a hacer con la ruta algo más que preguntar por ella. Ambas almacenan en caché su respuesta la primera vez que leemos una propiedad como Exists, así que esa respuesta puede quedar desactualizada, y Refresh() es lo que la actualiza. Esa caché es un costo cuando solo queremos responder a una pregunta y un ahorro cuando queremos responder a diez.

Usa File.GetAttributes() cuando también importen los demás bits de atributos, como oculto y solo lectura, y protégelo con una comprobación previa, porque lanza una excepción si la ruta no existe.

La tabla pone las seis llamadas una junto a otra, con lo que devuelve cada una para una ruta que no existe y para un directorio:

LlamadaPregunta que respondeRuta inexistenteSe le pasa un directorioDisponible desde
File.Exists(path)¿Es un archivo existente?falsefalse.NET Framework 1.0
Directory.Exists(path)¿Es un directorio existente?falsetrue.NET Framework 1.0
Path.Exists(path)¿Hay algo aquí, sea archivo o directorio?falsetrue.NET 7
new FileInfo(path).Exists¿Es un archivo existente?falsefalse.NET Framework 1.0
new DirectoryInfo(path).Exists¿Es un directorio existente?falsetrue.NET Framework 1.0
File.GetAttributes(path)¿Qué bits de atributos están activados, incluido Directory?lanza una excepcióndevuelve atributos con Directory activado.NET Framework 1.0

Todas las llamadas de la tabla consultan el sistema de archivos real, lo que hace que el código que las rodea sea difícil de someter a pruebas unitarias. Explicamos cómo probar código que usa el sistema de archivos sin tocar el disco en un artículo aparte.

La figura coloca las dos comprobaciones Exists() sobre un mismo eje, y la rama ámbar de abajo es el caso en el que ambas devuelven false:

Flujo que va de una cadena de ruta a tres resultados: directorio, archivo o no existe nada (o no podemos leerla).

Conclusión

En resumen, hemos visto tres formas de comprobar si una ruta es un archivo o un directorio en C#. Podemos comprobar si una ruta existe como archivo o como directorio con las clases File, Directory, FileInfo y DirectoryInfo. Después, hemos visto otra posibilidad con FileAttributes. Podemos obtener los FileAttributes de la ruta y comprobar si el indicador Directory está activado.

Una vez que sabemos qué es una ruta, suelen surgir dos preguntas: cómo contar los archivos que contiene cuando es un directorio, y si otro proceso tiene abierto el archivo cuando es un archivo.

Probado con .NET 10.0.10.