本系列第三篇聚焦進階 I/O 操作:非同步檔案存取 (避免 UI 凍結)、臨時檔案/隨機檔名產生、獨立儲存區 (Isolated Storage,無需檔案系統權限)、FileSystemWatcher 即時監視檔案變更。所有範例採用 using 區塊確保資源釋放,並說明效能調校關鍵參數。
適用場景:大檔案讀取、網路磁碟、不想阻塞 UI 執行緒。.NET 4.5+ 建議改用 async/await (ReadAsync/WriteAsync),此處示範 APM 模式以相容 .NET 2.0/3.5。
using System;
using System.IO;
using System.Threading;
public class AsyncFileReader : IDisposable
{
private readonly FileStream _stream;
private readonly byte[] _buffer;
private readonly int _bufferSize;
private readonly Action _onDataRead;
private readonly Action _onError;
private readonly Action _onCompleted;
public AsyncFileReader(string filePath, int bufferSize,
Action onDataRead, Action onError, Action onCompleted)
{
_bufferSize = bufferSize;
_buffer = new byte[bufferSize];
_onDataRead = onDataRead;
_onError = onError;
_onCompleted = onCompleted;
// FileOptions.Asynchronous 關鍵:啟用 I/O 完成埠 (IOCP)
_stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read,
bufferSize, FileOptions.Asynchronous);
}
public void Start()
{
try
{
_stream.BeginRead(_buffer, 0, _bufferSize, ReadCallback, null);
}
catch (Exception ex) { _onError(ex); }
}
private void ReadCallback(IAsyncResult ar)
{
try
{
int bytesRead = _stream.EndRead(ar);
if (bytesRead > 0)
{
_onDataRead(_buffer, bytesRead);
// 遞迴讀取下一區塊
_stream.BeginRead(_buffer, 0, _bufferSize, ReadCallback, null);
}
else
{
_onCompleted();
}
}
catch (Exception ex) { _onError(ex); }
}
public void Dispose() => _stream?.Dispose();
}
var reader = new AsyncFileReader(@"C:\LargeFile.dat", 8192,
onDataRead: (buffer, count) =>
{
// 處理資料區塊 (如寫入另一檔案、網路傳送、解析)
// 注意:此回呼在 ThreadPool 執行緒,若更新 UI 需 Invoke/BeginInvoke
},
onError: ex => MessageBox.Show($"讀取錯誤: {ex.Message}"),
onCompleted: () => Console.WriteLine("讀取完成"));
reader.Start();
// 主執行緒可繼續處理 UI 事件
// 結束時 reader.Dispose()
public async Task ProcessLargeFileAsync(string path)
{
using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 8192, true))
{
var buffer = new byte[8192];
int read;
while ((read = await fs.ReadAsync(buffer, 0, buffer.Length)) > 0)
{
await ProcessChunkAsync(buffer, read);
}
}
}
| 方法 | 行為 | 回傳 | 適用場景 |
|---|---|---|---|
| Path.GetTempFileName() | 在 %TEMP% 建立實體 0 位元組檔案,保證唯一 | 完整路徑 | 需立即寫入、稍後刪除 |
| Path.GetRandomFileName() | 產生隨機檔名 (如 "abc123.tmp"),不建立檔案 | 檔名字串 | 需自行決定路徑、避免衝突 |
| Path.GetTempPath() | 取得目前使用者臨時目錄路徑 | 目錄路徑 | 自行組合檔名 |
string tempFile = Path.GetTempFileName(); // 例: C:\Users\John\AppData\Local\Temp\tmp1234.tmp
try
{
using (var fs = new FileStream(tempFile, FileMode.Open, FileAccess.Write))
using (var bw = new BinaryWriter(fs))
{
bw.Write("臨時資料內容");
bw.Write(DateTime.Now.Ticks);
}
// 呼叫外部程式處理、上傳、列印等
Process.Start("notepad.exe", tempFile);
}
finally
{
if (File.Exists(tempFile)) File.Delete(tempFile); // 確保清理
}
適用場景:ClickOnce 應用、網頁應用、受限權限環境 (無 FileIOPermission)、使用者層級資料隔離 (不需管理路徑)。
IsolatedStorageFile:虛擬檔案系統根目錄 (User/Assembly/Domain/Roaming 範圍)IsolatedStorageFileStream:衍生自 FileStream,用法相同
using System.IO;
using System.IO.IsolatedStorage;
public class UserSettingsStore
{
private const string FileName = "settings.dat";
private const string DirName = "MyApp";
public void Save(SettingsData data)
{
// 取得「目前使用者 + 目前組件」的獨立存放區
using (var store = IsolatedStorageFile.GetUserStoreForAssembly())
{
if (!store.DirectoryExists(DirName)) store.CreateDirectory(DirName);
using (var stream = new IsolatedStorageFileStream(
Path.Combine(DirName, FileName), FileMode.Create, store))
using (var writer = new BinaryWriter(stream))
{
writer.Write(data.Version);
writer.Write(data.LastLogin.Ticks);
writer.Write(data.PreferencesJson);
}
}
}
public SettingsData Load()
{
using (var store = IsolatedStorageFile.GetUserStoreForAssembly())
{
string path = Path.Combine(DirName, FileName);
if (!store.FileExists(path)) return new SettingsData(); // 預設值
using (var stream = new IsolatedStorageFileStream(path, FileMode.Open, store))
using (var reader = new BinaryReader(stream))
{
return new SettingsData
{
Version = reader.ReadInt32(),
LastLogin = new DateTime(reader.ReadInt64()),
PreferencesJson = reader.ReadString()
};
}
}
}
public long GetUsedSize()
{
using (var store = IsolatedStorageFile.GetUserStoreForAssembly())
return store.CurrentSize; // 位元組
}
}
| 方法 | 範圍 | 隔離層級 | 典型用途 |
|---|---|---|---|
| GetUserStoreForAssembly() | User + Assembly | 同一使用者、同一組件 (強名稱/URL 證據) | 應用程式設定、快取 |
| GetUserStoreForDomain() | User + AppDomain | 同一使用者、同一應用程式域 | 插件/增益集隔離資料 |
| GetMachineStoreForAssembly() | Machine + Assembly | 全機器、同一組件 | 共用授權檔、系統級快取 |
| GetStore(IsolatedStorageScope.User | Assembly, ...) | 自訂組合 | 依證據類型 (StrongName, Url, Publisher) | 精細控制 |
即時偵測目錄/檔案 建立、刪除、變更、重命名。注意:事件在 ThreadPool 執行緒引發,更新 UI 需跨執行緒封送。
using System;
using System.IO;
using System.Windows.Forms; // 假設 WinForm
public class DirectoryMonitor : IDisposable
{
private readonly FileSystemWatcher _watcher;
private readonly Action _onChanged;
public DirectoryMonitor(string path, string filter, Action onChanged)
{
_onChanged = onChanged;
_watcher = new FileSystemWatcher(path, filter)
{
IncludeSubdirectories = true,
NotifyFilter = NotifyFilters.FileName | NotifyFilters.DirName
| NotifyFilters.LastWrite | NotifyFilters.Size
| NotifyFilters.Attributes | NotifyFilters.Security
};
_watcher.Created += OnEvent;
_watcher.Deleted += OnEvent;
_watcher.Changed += OnEvent;
_watcher.Renamed += (s, e) => _onChanged($"{e.OldFullPath} → {e.FullPath}", WatcherChangeTypes.Renamed);
_watcher.Error += (s, e) =>
{
// 緩衝區溢位時重置
if (e.GetException() is InternalBufferOverflowException)
{
_watcher.EnableRaisingEvents = false;
_watcher.EnableRaisingEvents = true;
}
};
}
private void OnEvent(object sender, FileSystemEventArgs e)
{
_onChanged(e.FullPath, e.ChangeType);
}
public void Start() => _watcher.EnableRaisingEvents = true;
public void Stop() => _watcher.EnableRaisingEvents = false;
public void Dispose()
{
Stop();
_watcher.Dispose();
}
}
public partial class MainForm : Form
{
private DirectoryMonitor _monitor;
private void btnStartMonitor_Click(object sender, EventArgs e)
{
_monitor = new DirectoryMonitor(@"C:\WatchDir", "*.txt", OnFileChanged);
_monitor.Start();
}
private void OnFileChanged(string path, WatcherChangeTypes type)
{
// 必須 Invoke 回 UI 執行緒
if (InvokeRequired)
{
BeginInvoke(new Action(OnFileChanged), path, type);
return;
}
lstLog.Items.Add($"[{DateTime.Now:HH:mm:ss}] {type}: {path}");
lstLog.TopIndex = lstLog.Items.Count - 1;
}
protected override void OnFormClosing(FormClosingEventArgs e)
{
_monitor?.Dispose();
base.OnFormClosing(e);
}
}
| 屬性/事件 | 說明 | 注意事項 |
|---|---|---|
| NotifyFilter | 指定監視哪些變更類型 (Flags) | 預設只含 FileName/DirName/LastWrite,**務必加入 Size/Attributes** 否則修改檔案內容不觸發 Changed |
| IncludeSubdirectories | 是否遞迴監視子目錄 | true 會大幅增加事件數量,注意效能 |
| InternalBufferOverflowException | 事件產生速度超過處理速度 | 增大 InternalBufferSize (預設 8KB, 最大 64KB)、簡化事件處理邏輯、啟用 IncludeSubdirectories=false |
| Changed 事件重複觸發 | 某些編輯器 (如 Notepad) 存檔會觸發多次 | 應用去抖動邏輯 (如 500ms 內同路徑同類型只處理一次) |
| 網路磁碟監視 | SMB 共享支援有限 | 需遠端主機啟用 "File Change Notification" 服務,不穩定建議改輪詢 |
| 類別 | 基底/介面 | 資料型態 | 編碼支援 | 典型用途 |
|---|---|---|---|---|
| FileStream | Stream | Byte[] | 無 (原始位元組) | 所有檔案 I/O 基礎、非同步、隨機存取 |
| StreamReader/Writer | TextReader/Writer | String/Char | UTF-8 (預設)、Unicode、ASCII、自訂 | 文字檔逐行讀寫、CSV、Log、JSON |
| BinaryReader/Writer | Object | Primitive Types | UTF-7 (字串) | 二進位格式、自訂協定、高效序列化 |
| StringReader/Writer | TextReader/Writer | String (記憶體) | UTF-16 (內部) | 字串模擬檔案、單元測試、記憶體緩衝 |
| MemoryStream | Stream | Byte[] (記憶體) | 無 | 暫存資料、序列化中介、圖片處理 |
| BufferedStream | Stream | Byte[] | 無 | 包裝非緩衝 Stream (如 NetworkStream) 提升效能 |
| GZipStream/DeflateStream | Stream | Byte[] | 無 | 串流壓縮/解壓縮 |
| CryptoStream | Stream | Byte[] | 無 | 串流加密/解密 |
需要非同步不阻塞 UI? ──是──→ .NET 4.5+ 用 async/await (ReadAsync/WriteAsync)
.NET 2.0/3.5 用 FileStream.BeginRead/EndRead (IOCP)
│
需要無權限寫入使用者資料? ──是──→ IsolatedStorage (GetUserStoreForAssembly)
│
需要即時偵測檔案變更? ──是──→ FileSystemWatcher (注意緩衝區溢位、去抖動)
│
需要臨時檔案自動清理? ──是──→ Path.GetTempFileName() + finally File.Delete()
│
一般文字檔讀寫 ──→ StreamReader/Writer (指定 Encoding)
一般二進位讀寫 ──→ BinaryReader/Writer
大檔案/隨機存取 ──→ FileStream (Position/Seek)
記憶體高速緩衝 ──→ MemoryStream