Monitor configuration files and reload when they change:
usingCocoar.FileSystem;publicclassConfigurationService{privateResilientFileSystemMonitor?_monitor;publicvoidStartMonitoring(stringconfigDirectory){_monitor=newResilientFileSystemMonitor(newResilientFileSystemMonitor.Options{Path=configDirectory,Filter="*.json",EnablePollingFallback=true,PollingInterval=TimeSpan.FromSeconds(10),DebounceTime=TimeSpan.FromMilliseconds(500)});_monitor.Changed+=OnConfigChanged;_monitor.Created+=OnConfigChanged;_monitor.Deleted+=OnConfigDeleted;_monitor.Renamed+=OnConfigRenamed;}privatevoidOnConfigChanged(object?sender,FileSystemEventArgse){Console.WriteLine($"Configuration changed: {e.FullPath}");ReloadConfiguration(e.FullPath);}privatevoidOnConfigDeleted(object?sender,FileSystemEventArgse){Console.WriteLine($"Configuration deleted: {e.FullPath}");RemoveConfiguration(e.FullPath);}privatevoidOnConfigRenamed(object?sender,RenamedEventArgse){Console.WriteLine($"Configuration renamed: {e.OldFullPath} → {e.FullPath}");RemoveConfiguration(e.OldFullPath);ReloadConfiguration(e.FullPath);}privatevoidReloadConfiguration(stringfilePath){// Reload logic here}privatevoidRemoveConfiguration(stringfilePath){// Cleanup logic here}}Monitor a certificate directory with error handling:
usingCocoar.FileSystem;publicclassCertificateWatcher{privatereadonlyILogger<CertificateWatcher>_logger;privateResilientFileSystemMonitor?_monitor;publicCertificateWatcher(ILogger<CertificateWatcher>logger){_logger=logger;}publicvoidWatch(stringcertificatePath){// Monitor multiple certificate formats_monitor=ResilientFileSystemMonitor.Watch(certificatePath).WithFilter("*.pfx","*.p12","*.cer")// Multiple formats.WithDebounce(TimeSpan.FromSeconds(2)).OnChanged(OnCertificateChanged).OnError(OnError).OnModeChanged(OnModeChanged).Build();}privatevoidOnCertificateChanged(object?sender,FileSystemEventArgse){_logger.LogInformation("Certificate changed: {Path}",e.FullPath);ReloadCertificate(e.FullPath);}privatevoidOnError(object?sender,ErrorEventArgse){_logger.LogWarning(e.GetException(),"Certificate watcher error");}privatevoidOnModeChanged(object?sender,MonitorModeChangedEventArgse){_logger.LogInformation("Monitor mode changed to {Mode}: {Reason}",e.NewMode,e.Reason);}privatevoidReloadCertificate(stringfilePath){// Certificate reload logic}}Monitor a directory that might not exist at startup (Docker volume):
usingCocoar.FileSystem;publicclassDockerVolumeWatcher{publicvoidMonitorDockerVolume(){varvolumePath="/app/data";// Docker volume mount pointvarmonitor=newResilientFileSystemMonitor(newResilientFileSystemMonitor.Options{Path=volumePath,Filter="*.dat",EnablePollingFallback=true,// Will poll until volume appearsPollingInterval=TimeSpan.FromSeconds(5),AutoRecoverFromErrors=true});monitor.ModeChanged+=(s,e)=>{Console.WriteLine($"Mode: {e.NewMode} - {e.Reason}");};monitor.Created+=(s,e)=>{Console.WriteLine($"File created: {e.Name}");ProcessNewFile(e.FullPath);};}privatevoidProcessNewFile(stringfilePath){// Process file logic}}Monitor a network share with resilient error handling:
usingCocoar.FileSystem;publicclassNetworkShareMonitor{publicvoidMonitorNetworkShare(stringuncPath){varmonitor=newResilientFileSystemMonitor(newResilientFileSystemMonitor.Options{Path=uncPath,// e.g., @"\\server\share\folder"Filter="*.*",EnablePollingFallback=true,PollingInterval=TimeSpan.FromSeconds(15),AutoRecoverFromErrors=true,DebounceTime=TimeSpan.FromSeconds(1)});monitor.Changed+=(s,e)=>Console.WriteLine($"Changed: {e.Name}");monitor.Created+=(s,e)=>Console.WriteLine($"Created: {e.Name}");monitor.Deleted+=(s,e)=>Console.WriteLine($"Deleted: {e.Name}");monitor.Renamed+=(s,e)=>Console.WriteLine($"Renamed: {e.OldName} → {e.Name}");monitor.Error+=(s,e)=>{Console.WriteLine($"Network error: {e.GetException().Message}");// Will automatically fall back to polling};monitor.ModeChanged+=(s,e)=>{Console.WriteLine($"Switched to {e.NewMode} mode");};}}Complete service with dependency injection:
usingCocoar.FileSystem;usingMicrosoft.Extensions.Hosting;usingMicrosoft.Extensions.Logging;publicclassHotReloadService:IHostedService,IDisposable{privatereadonlyILogger<HotReloadService>_logger;privateResilientFileSystemMonitor?_monitor;publicHotReloadService(ILogger<HotReloadService>logger){_logger=logger;}publicTaskStartAsync(CancellationTokencancellationToken){varwatchPath=Path.Combine(AppContext.BaseDirectory,"configs");_monitor=newResilientFileSystemMonitor(newResilientFileSystemMonitor.Options{Path=watchPath,Filter="*.json",EnablePollingFallback=true,PollingInterval=TimeSpan.FromSeconds(5),DebounceTime=TimeSpan.FromMilliseconds(500)});_monitor.Changed+=OnFileChanged;_monitor.Created+=OnFileChanged;_monitor.Error+=OnError;_logger.LogInformation("Hot reload service started for {Path}",watchPath);returnTask.CompletedTask;}publicTaskStopAsync(CancellationTokencancellationToken){_logger.LogInformation("Hot reload service stopping");returnTask.CompletedTask;}privatevoidOnFileChanged(object?sender,FileSystemEventArgse){_logger.LogInformation("Configuration file changed: {Name}",e.Name);// Trigger configuration reload}privatevoidOnError(object?sender,ErrorEventArgse){_logger.LogError(e.GetException(),"File system monitor error");}publicvoidDispose(){_monitor?.Dispose();}}Unit testing with the monitor:
usingCocoar.FileSystem;usingXunit;publicclassFileMonitorTests{[Fact]publicasyncTaskShouldDetectFileChanges(){// ArrangevartempDir=Path.Combine(Path.GetTempPath(),Guid.NewGuid().ToString());Directory.CreateDirectory(tempDir);varchangedFile="";vareventReceived=newTaskCompletionSource<bool>();varmonitor=newResilientFileSystemMonitor(newResilientFileSystemMonitor.Options{Path=tempDir,Filter="*.txt"});monitor.Created+=(s,e)=>{changedFile=e.Name;eventReceived.SetResult(true);};// ActawaitFile.WriteAllTextAsync(Path.Combine(tempDir,"test.txt"),"content");awaiteventReceived.Task.WaitAsync(TimeSpan.FromSeconds(2));// AssertAssert.Equal("test.txt",changedFile);// Cleanupmonitor.Dispose();Directory.Delete(tempDir,true);}}Read sensitive data as bytes to avoid immutable strings in memory:
usingCocoar.FileSystem;usingSystem.Security.Cryptography;usingSystem.Text;publicclassSecureConfigReader{publicSecretConfigReadSecrets(stringpath){byte[]?configBytes=null;try{// Read with shared access - works even if file is locked by another processconfigBytes=FileReader.ReadAllBytes(path);// Decrypt or parse the sensitive contentvarconfig=ParseSecretConfig(configBytes);returnconfig;}finally{// CRITICAL: Zero out the byte array to remove from memoryif(configBytes!=null){Array.Clear(configBytes,0,configBytes.Length);}}}privateSecretConfigParseSecretConfig(byte[]data){// Parse your config herevarjson=Encoding.UTF8.GetString(data);returnJsonSerializer.Deserialize<SecretConfig>(json)!;}}Strip UTF-8 BOM automatically when reading text files:
usingCocoar.FileSystem;publicclassTextFileReader{publicstringReadTextFile(stringpath){// Strip BOM if present - file saved with BOM will be read correctlybyte[]bytes=FileReader.ReadAllBytes(path,stripUtf8Bom:true);// Now decode as UTF-8 without BOMreturnEncoding.UTF8.GetString(bytes);}}Handle optional configuration files gracefully:
usingCocoar.FileSystem;publicclassConfigurationManager{publicvoidLoadConfiguration(){// Try to load optional override filebyte[]?overrideConfig=FileReader.TryReadAllBytes(@"C:\config\override.json");if(overrideConfig!=null){Console.WriteLine("Override configuration found, applying...");ApplyOverrides(overrideConfig);}else{Console.WriteLine("No override configuration, using defaults");}// Main config is requiredbyte[]mainConfig=FileReader.ReadAllBytes(@"C:\config\main.json");ApplyMainConfig(mainConfig);}privatevoidApplyOverrides(byte[]config){/* ... */}privatevoidApplyMainConfig(byte[]config){/* ... */}}Read binary files that may be written by other processes:
usingCocoar.FileSystem;publicclassLogProcessor{publicvoidProcessActiveLogFile(stringlogPath){// FileReader uses FileShare.ReadWrite, allowing read even when// the logging process has the file open for writingbyte[]logData=FileReader.ReadAllBytes(logPath);ProcessLogEntries(logData);}privatevoidProcessLogEntries(byte[]data){// Process the binary log entriesConsole.WriteLine($"Processing {data.Length} bytes of log data");}}Example of reading credentials with proper cleanup:
usingCocoar.FileSystem;usingSystem.Security;publicclassCredentialReader{publicSecureStringReadPassword(stringpasswordFilePath){byte[]?passwordBytes=null;try{passwordBytes=FileReader.ReadAllBytes(passwordFilePath,stripUtf8Bom:true);// Convert to SecureString character by charactervarsecurePassword=newSecureString();varpassword=Encoding.UTF8.GetString(passwordBytes).Trim();foreach(charcinpassword){securePassword.AppendChar(c);}securePassword.MakeReadOnly();returnsecurePassword;}finally{// Zero out sensitive data from memoryif(passwordBytes!=null){Array.Clear(passwordBytes,0,passwordBytes.Length);}}}}Search for source files while excluding build and dependency folders:
usingCocoar.FileSystem;publicclassCodeAnalyzer{publicList<string>FindSourceFiles(stringprojectRoot){// Find all C# files, excluding common build/dependency foldersvarsourceFiles=FileSearcher.Search(projectRoot,"*.cs").Excluding("bin","obj","packages","node_modules",".git").Recursively().ToList();Console.WriteLine($"Found {sourceFiles.Count} C# files");returnsourceFiles;}}Use LINQ to process files as they're discovered without loading all results into memory:
usingCocoar.FileSystem;publicclassLargeFileFinder{publicvoidFindAndProcessLargeFiles(stringsearchPath){// Lazy evaluation - files are found and processed one at a timevarlargeLogFiles=FileSearcher.InDirectory(searchPath).WithPattern("*.log").WithMaxDepth(3).Where(file =>newFileInfo(file).Length>10_000_000)// > 10 MB.OrderByDescending(file =>newFileInfo(file).Length).Take(10);// Only get top 10foreach(varfileinlargeLogFiles){Console.WriteLine($"Large file: {file} ({newFileInfo(file).Length:N0} bytes)");ArchiveFile(file);}}privatevoidArchiveFile(stringfilePath){/* ... */}}Control how deep the search recurses into subdirectories:
usingCocoar.FileSystem;publicclassConfigurationScanner{publicvoidScanConfigs(stringappRoot){// Only search current directory (no subdirectories)varrootConfigs=FileSearcher.Search(appRoot,"*.json").WithMaxDepth(0)// 0 = current directory only.ToList();// Search up to 2 levels deepvarnestedConfigs=FileSearcher.Search(appRoot,"*.json").WithMaxDepth(2)// appRoot + 2 levels of subdirectories.Excluding("node_modules").ToList();// Unlimited depth (all subdirectories)varallConfigs=FileSearcher.Search(appRoot,"*.json").Recursively()// Same as .WithMaxDepth(null).ToList();Console.WriteLine($"Root: {rootConfigs.Count}, Nested: {nestedConfigs.Count}, All: {allConfigs.Count}");}}Combine multiple searches efficiently:
usingCocoar.FileSystem;publicclassAssetScanner{publicDictionary<string,List<string>>CategorizeAssets(stringprojectPath){varresult=newDictionary<string,List<string>>();// Imagesresult["images"]=FileSearcher.InDirectory(Path.Combine(projectPath,"assets")).WithPattern("*.png").Recursively().ToList();// Stylesheetsresult["styles"]=FileSearcher.InDirectory(Path.Combine(projectPath,"styles")).WithPattern("*.css").Excluding("dist","build").ToList();// Scriptsresult["scripts"]=FileSearcher.InDirectory(Path.Combine(projectPath,"src")).WithPattern("*.js").WithMaxDepth(5).ToList();returnresult;}}Combine FileSearcher with LINQ and FileInfo for advanced filtering:
usingCocoar.FileSystem;publicclassRecentFilesFinder{publicList<string>FindRecentlyModifiedFiles(stringsearchPath,TimeSpanmaxAge){varcutoffTime=DateTime.Now-maxAge;// Lazy evaluation - only checks files that match the patternvarrecentFiles=FileSearcher.InDirectory(searchPath).WithPattern("*.*").Recursively().Where(file =>File.GetLastWriteTime(file)>cutoffTime).OrderByDescending(file =>File.GetLastWriteTime(file)).ToList();Console.WriteLine($"Found {recentFiles.Count} files modified in the last {maxAge.TotalHours} hours");returnrecentFiles;}}usingCocoar.FileSystem;publicclassPerformanceExample{publicvoidLazyEvaluation(){// Lazy - no I/O happens until you iteratevarquery=FileSearcher.Search(@"C:\Windows","*.dll").Recursively();// I/O happens here, but stops after finding 5 filesvarfirstFive=query.Take(5).ToList();Console.WriteLine("Only enumerated files until we found 5 matches");}publicvoidEagerEvaluation(){// Eager - ToList() forces full enumeration immediatelyvarallFiles=FileSearcher.Search(@"C:\Program Files","*.exe").Recursively().ToList();// All files loaded into memory at onceConsole.WriteLine($"Loaded all {allFiles.Count} files into memory");}}