FileSystemWatcher vigila un directorio y genera un evento cuando se crea, se elimina, se renombra o se escribe un archivo dentro de él. El sistema operativo detecta cada cambio, y la clase es el envoltorio de .NET que nos lo hace llegar.
Filter elige de qué archivos nos enteramos y NotifyFilter elige qué tipos de cambio. No se dispara nada hasta que EnableRaisingEvents se establece en true.
Dos comportamientos sorprenden una vez que el observador está en marcha: un solo guardado puede producir tres eventos, y un directorio con mucha actividad puede producir menos eventos de los que debería.
¿Qué es FileSystemWatcher en C#?
FileSystemWatcher es una clase del espacio de nombres System.IO que vigila un directorio y genera un evento cada vez que algo cambia dentro de él. No sondea el disco: el sistema operativo notifica cada cambio y la clase nos lo entrega.
Tres opciones deciden de qué nos enteramos. Path es el directorio que se vigila. Filter limita la vigilancia a un patrón de archivo como *.txt. NotifyFilter decide qué tipos de cambio cuentan, desde el nombre de un archivo hasta su configuración de seguridad.
La clase genera cinco eventos: Created, Deleted, Changed, Renamed y Error. Ninguno se dispara hasta que establecemos EnableRaisingEvents en true.
El observador funciona en un disco local o en una carpeta compartida en red, y llega también a los subdirectorios cuando lo activamos con IncludeSubdirectories. Nunca nos dice quién hizo el cambio ni qué contiene ahora el archivo, solo que una ruta que nos interesa apareció, desapareció, se renombró o recibió una escritura.
Veamos cómo configurar FileSystemWatcher en el proyecto:
public class TextFileManager
{
private readonly FileSystemWatcher _fileSystemWatcher;
public TextFileManager(string rootDirectory)
{
_fileSystemWatcher = new FileSystemWatcher(rootDirectory);
}
}
Podemos inicializar FileSystemWatcher simplemente pasándole la ruta que queremos supervisar. A lo largo del artículo, usaremos nuestra clase TextFileManager para configurar la instancia de FileSystemWatcher y trabajar con ella. Por supuesto, siempre puedes descargar nuestro código fuente para ver la implementación completa.
El ejemplo provoca sus eventos con llamadas normales como File.WriteAllLines(), File.Move() y File.Delete(), y nuestro artículo sobre las clases File y Directory las explica en detalle.
¿Cómo filtrar las notificaciones de FileSystemWatcher?
El filtrado lo hacen dos propiedades, y cada una responde a una pregunta distinta. Filter decide qué archivos vigilamos. NotifyFilter decide qué cambios en esos archivos nos llegan.
Filter admite un único patrón con comodines y su valor predeterminado es *, que vigila todos los archivos. Si lo establecemos en *.txt, el observador se limita a los archivos de texto. Para varios patrones a la vez existe Filters, una colección, así que una sola instancia puede vigilar *.txt y *.log sin que tengamos que crear dos observadores.
NotifyFilter admite una combinación de valores de NotifyFilters con OR bit a bit, y ya viene con un valor asignado: el predeterminado es la combinación de LastWrite, FileName y DirectoryName. Añadir Size o Attributes amplía la vigilancia, y dejar fuera un valor la reduce.
Una vigilancia acotada es, además, la más segura. Cada notificación que acepta el observador ocupa espacio en un búfer interno de tamaño fijo, así que cada valor adicional de NotifyFilters y cada subdirectorio adicional hacen más probable un desbordamiento y, con él, la pérdida de un evento.
Filter
Filter es una propiedad de FileSystemWatcher que nos permite supervisar archivos concretos indicando un patrón de archivo. El valor predeterminado de Filter es *, que supervisa todos los archivos, y *.* o una cadena vacía hacen lo mismo.
Vamos a configurar el tipo de archivos que queremos supervisar:
private void ConfigureFilters()
{
_fileSystemWatcher.Filter = "*.txt";
}
Ahora _fileSystemWatcher solo supervisa los cambios en archivos de texto.
Al establecer Filter, su patrón pasa a la colección Filters, así que, si después añadimos *.log a Filters, el mismo observador notifica los cambios tanto en los archivos de texto como en los de log.

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 gratisPDF gratuito. Un solo correo para enviártelo. Puedes darte de baja cuando quieras.
NotifyFilter
NotifyFilter es otra propiedad de FileSystemWatcher que nos permite indicar y supervisar cambios concretos en el sistema de archivos. Podemos configurar NotifyFilter para que escuche varios tipos de cambios con el operador OR bit a bit ("|"). De forma predeterminada, el valor de NotifyFilter es una combinación con OR bit a bit de LastWrite, DirectoryName y FileName, pero podemos ampliarla:
private void ConfigureFilters()
{
...
_fileSystemWatcher.NotifyFilter = NotifyFilters.Attributes
| NotifyFilters.CreationTime
| NotifyFilters.DirectoryName
| NotifyFilters.FileName
| NotifyFilters.LastAccess
| NotifyFilters.LastWrite
| NotifyFilters.Security
| NotifyFilters.Size;
}
Si combinamos valores de NotifyFilters, podemos ampliar el abanico de cambios que supervisamos. La enumeración tiene ocho valores, y la última columna indica cuáles son los tres que ya incluye el valor predeterminado:
Valor de NotifyFilters | Cambio que notifica | ¿Incluido de forma predeterminada? |
|---|---|---|
FileName | El nombre del archivo | Sí |
DirectoryName | El nombre del directorio | Sí |
LastWrite | La fecha de la última escritura en el archivo o la carpeta | Sí |
Attributes | Los atributos del archivo o la carpeta | No |
Size | El tamaño del archivo o la carpeta | No |
LastAccess | La fecha en que se abrió por última vez el archivo o la carpeta | No |
CreationTime | La hora en que se creó el archivo o la carpeta | No |
Security | La configuración de seguridad del archivo o la carpeta | No |
También podemos supervisar los cambios dentro de los subdirectorios con la propiedad IncludeSubdirectories:
private void ConfigureFilters()
{
...
_fileSystemWatcher.IncludeSubdirectories = true;
...
}
¿Qué eventos genera FileSystemWatcher?
FileSystemWatcher ofrece 5 tipos de eventos distintos que podemos usar. En concreto: Created, Deleted, Changed, Renamed y Error.
Los cinco manejadores que siguen pertenecen a la clase TextFileManager del ejemplo, y ninguno se ejecuta hasta que se establece EnableRaisingEvents, que es lo que hace el último fragmento de código de esta sección.
Created
Podemos configurarlo para que genere un evento cuando se crea un archivo o un directorio:
private void ConfigureEvents()
{
_fileSystemWatcher.Created += HandleCreated;
}
private void HandleCreated(object sender, FileSystemEventArgs e)
{
Console.WriteLine($"Create: {e.FullPath}");
}
Deleted
Podemos configurarlo para que genere un evento cuando se elimina un archivo o un directorio:
private void ConfigureEvents()
{
_fileSystemWatcher.Deleted += HandleDeleted;
...
}
private void HandleDeleted(object sender, FileSystemEventArgs e)
{
Console.WriteLine($"Delete: {e.FullPath}");
}
Changed
Podemos configurarlo para que genere un evento cuando cambian el tamaño, los atributos del sistema, la última actualización, la hora del último acceso o los permisos de un archivo o un directorio:
private void ConfigureEvents()
{
_fileSystemWatcher.Changed += HandleChanged;
...
}
private void HandleChanged(object sender, FileSystemEventArgs e)
{
Console.WriteLine($"Change: {Enum.GetName(e.ChangeType)} {e.FullPath}");
}
Los manejadores de los eventos Created, Deleted y Changed tienen firmas de método similares.
Renamed
Podemos usarlo para generar un evento cuando cambia el nombre de un archivo o de un directorio:
private void ConfigureEvents()
{
_fileSystemWatcher.Renamed += HandleRenamed;
...
}
private void HandleRenamed(object sender, RenamedEventArgs e)
{
Console.WriteLine($"Rename: {e.OldName} => {e.Name}");
}
Los manejadores del evento Renamed tienen una firma de método distinta de la de los manejadores de los eventos Created, Deleted y Changed. El segundo argumento, de tipo RenamedEventArgs, contiene las propiedades con el nombre antiguo y el nombre nuevo del archivo o directorio afectado.
Error
Por distintos motivos, FileSystemWatcher puede llegar a un estado en el que ya no puede supervisar los cambios del sistema de archivos en la ruta indicada. Para generar un evento en este caso, usamos el manejador del evento Error:
private void ConfigureEvents()
{
_fileSystemWatcher.Error += HandleError;
...
}
private void HandleError(object sender, ErrorEventArgs e)
{
Console.WriteLine($"Error: {e.GetException().Message}");
}
Por último, para que _fileSystemWatcher pueda generar eventos, tenemos que establecer la propiedad EnableRaisingEvents en true:
private void ConfigureEvents()
{
...
_fileSystemWatcher.EnableRaisingEvents = true;
}
Para empezar a vigilar, llamamos a ConfigureFilters() y luego a ConfigureEvents() al final del constructor de TextFileManager, justo después de crear el observador.
Los eventos de FileSystemWatcher se generan en respuesta a los cambios correspondientes del sistema de archivos dentro de la ruta supervisada y de acuerdo con los filtros. Estos son los cinco eventos juntos, con el argumento que recibe cada manejador:
| Evento | Argumento del manejador | Se genera cuando |
|---|---|---|
Created | FileSystemEventArgs | Aparece en la ruta vigilada un archivo o directorio que coincide con el filtro |
Deleted | FileSystemEventArgs | Se elimina un archivo o directorio que coincide con el filtro |
Changed | FileSystemEventArgs | Cambian el contenido, el tamaño, los atributos, las marcas de tiempo o la configuración de seguridad de un elemento que coincide con el filtro |
Renamed | RenamedEventArgs (añade OldName y OldFullPath) | Se renombra un archivo o directorio que coincide con el filtro |
Error | ErrorEventArgs (llama a GetException()) | El observador pierde el rastro de los cambios, normalmente por un desbordamiento del búfer, o deja de vigilar porque ha desaparecido el propio directorio vigilado |
Las operaciones habituales del sistema de archivos, como mover o copiar, pueden generar más de un evento.
¿Por qué FileSystemWatcher genera varios eventos para un solo cambio?
Porque una acción en el sistema de archivos rara vez es una sola operación en el disco. Guardar un archivo en un editor puede escribir los bytes, actualizar la marca de tiempo y modificar los atributos, y cada una de esas operaciones es una notificación independiente.
Microsoft documenta este comportamiento. Mover un archivo de un directorio a otro puede generar varios eventos Changed junto con Created y Deleted, porque un movimiento se compone de varias operaciones más simples. Otros programas de la máquina también añaden eventos, en particular los antivirus, y el observador notifica esa actividad como cualquier otra.
Por eso, los manejadores tienen que tolerar las repeticiones. Una forma es recordar la última ruta y la marca de tiempo sobre las que actuamos e ignorar una repetición dentro de un intervalo corto. Otra es poner cada evento en una cola y dejar que un único consumidor decida qué vale la pena hacer.

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 gratisPDF gratuito. Un solo correo para enviártelo. Puedes darte de baja cuando quieras.
Además, Created se dispara en cuanto aparece el archivo, no cuando termina quien lo está escribiendo, así que leerlo de inmediato puede toparse con un archivo bloqueado o a medio escribir.

El búfer interno es el único punto de este recorrido en el que se pueden perder notificaciones.
La documentación de FileSystemWatcher lo dice sin rodeos: “Las operaciones habituales del sistema de archivos pueden generar más de un evento”. Su ejemplo es un archivo que se mueve de un directorio a otro, lo que, según indica, puede generar “varios eventos OnChanged y algunos eventos OnCreated y OnDeleted“.
Antes de que un manejador lea un archivo del que acaba de recibir aviso, puede comprobar si el archivo sigue en uso y volver a intentarlo más tarde si quien lo escribe todavía lo tiene abierto.
Para la cola, podemos pasar cada evento a una cola con Channels de .NET: el manejador escribe el evento en el canal y devuelve el control de inmediato, y un único consumidor lo lee a su propio ritmo.
La ruta de FileSystemEventArgs no indica si corresponde a un archivo o a un directorio. Un manejador que necesite saberlo tiene que comprobar por su cuenta si una ruta dada es un archivo o un directorio, y eso solo funciona mientras la ruta siga existiendo.
¿Cómo evitar que FileSystemWatcher pierda eventos?
Evitando que se desborde el búfer interno. El sistema operativo guarda las notificaciones de cambio en un búfer que crea el observador. El valor predeterminado de InternalBufferSize es 8192 bytes, y cada notificación ocupa hasta 16 bytes más el nombre del archivo. Cuando el búfer se llena, se pierden notificaciones y el observador genera Error.
Cuatro medidas evitan que se llene. Limita NotifyFilter a los cambios que de verdad manejas. Deja IncludeSubdirectories desactivada salvo que la necesites. Opta por nombres de archivo cortos en el directorio vigilado, porque el nombre es la parte de cada notificación cuyo tamaño varía. Mantén breves los manejadores y pasa el trabajo lento a otro hilo (thread).
Aumenta InternalBufferSize solo después de aplicar esas cuatro. Microsoft documenta 4 KB como mínimo y 64 KB como máximo, y la propiedad redondea en silencio a 4096 cualquier valor inferior a 4096 bytes. Además, el búfer sale de memoria no paginada, que no se puede pasar al disco, así que un búfer más grande supone un costo real para la máquina.
Para evitar que se desborde el búfer:
- Reducir al mínimo los tipos de cambios que queremos supervisar con
NotifyFilteryIncludeSubdirectories - Supervisar solo los archivos que interesan indicando el patrón de archivo. Evitar supervisar archivos con nombres largos, porque cada notificación ocupa hasta 16 bytes más su nombre de archivo
- Reducir al mínimo posible el código del manejador de eventos
- Si sigue sin haber espacio suficiente, aumentar el tamaño del búfer con la propiedad
InternalBufferSize - Manejar el evento
Error. RecibirInternalBufferOverflowExceptionsignifica que se perdieron notificaciones mientras el observador seguía en marcha, pero siEnableRaisingEventsha pasado afalse, como ocurre cuando se elimina el directorio vigilado, hay que volver a crear el observador
Conclusión
FileSystemWatcher necesita tres propiedades para notificar eventos del sistema de archivos (Path, Filter y NotifyFilter), además de EnableRaisingEvents para activarlo.
El resto del trabajo está en los manejadores. Deben dar por hecho que cualquier cambio puede llegar más de una vez y que un archivo recién creado todavía no está listo para leerse. La vigilancia debe seguir siendo acotada para que el búfer no se desborde y pierda notificaciones.
Un observador solo genera eventos hasta que se libera, así que en una aplicación de larga duración podemos ejecutar el observador dentro de una tarea en segundo plano que sea su propietaria mientras el host esté en ejecución.
Probado con .NET 10.0.10.