FileStream无法实现流畅接口因其构造即打开、Dispose即关闭且方法多返回void,违背Fluent要求的“构建可暂存、执行延迟”;FileOp通过解耦配置与I/O(如WithEncoding仅设参数、Execute统一触发)实现安全链式调用。

为什么直接用 FileStream 写不出流畅接口
因为 FileStream 本身是低层资源句柄,构造即打开、Dispose 即关闭,不支持链式调用;多数方法返回 void,无法接续。强行包装成 Fluent 时容易漏关流、重复关闭、或在异常路径下资源泄漏。
关键矛盾在于:Fluent API 要求「构建过程可暂存、执行延迟」,而 FileStream 是「即开即用、即用即管」。所以得把「打开文件」和「执行操作」解耦。
- 不要在构造器里打开文件 —— 改为
Open()或Execute()方法统一触发 - 所有中间方法(如
WithEncoding()、WithBufferSize(4096))只设配置,不碰 I/O - 最终必须有且仅有一个「执行点」,比如
ReadAllText()、WriteLines()或Process()
FileOp 类如何支持链式配置与安全执行
一个轻量但可控的 Fluent 入口类,不继承 Stream,也不封装 Stream 实例,而是持有路径、选项、委托等元数据:
public class FileOp
{
private readonly string _path;
private Encoding _encoding = Encoding.UTF8;
private int _bufferSize = 8192;
private Action<Stream> _action;
public FileOp(string path) => _path = path;
public FileOp WithEncoding(Encoding enc) { _encoding = enc; return this; }
public FileOp WithBufferSize(int size) { _bufferSize = size; return this; }
public string ReadAllText()
{
using var fs = new FileStream(_path, FileMode.Open, FileAccess.Read, FileShare.Read, _bufferSize);
using var reader = new StreamReader(fs, _encoding);
return reader.ReadToEnd();
}
public FileOp Do(Action<Stream> action)
{
_action = action;
return this;
}
public void Execute()
{
if (_action == null) throw new InvalidOperationException("No action configured.");
using var fs = new FileStream(_path, FileMode.Open, FileAccess.ReadWrite, FileShare.None, _bufferSize);
_action(fs);
}
}
这样调用就自然了:
new FileOp("log.txt")
.WithEncoding(Encoding.UTF8)
.Do(s => s.Write(new byte[]{1,2,3}, 0, 3))
.Execute();
什么时候该用 async 版 Fluent 接口
如果业务中大量处理大文件或网络存储(如 Azure Blob),同步阻塞会拖垮吞吐。此时 Fluent 接口必须区分同步/异步分支,不能混用 —— 否则 await 点不明确,容易死锁或忽略异常。
- 异步方法名加
Async后缀,如ReadAllTextAsync()、WriteAllLinesAsync() - 不要让
Do()接收Func<Stream, Task>—— 它会让调用方误以为「链式过程本身是异步的」,实际只是注册,真正 await 发生在ExecuteAsync() - 异步执行方法内部必须用
await using和ConfigureAwait(false),尤其在库代码中
示例:
public async Task<string> ReadAllTextAsync()
{
await using var fs = new FileStream(_path, FileMode.Open, FileAccess.Read, FileShare.Read, _bufferSize, useAsync: true);
await using var reader = new StreamReader(fs, _encoding);
return await reader.ReadToEndAsync().ConfigureAwait(false);
}
常见坑:缓存、重入与线程安全
Fluent 对象默认是「一次性的」—— 配置后执行完,不应再复用。否则会出现:
- 重复
Execute()导致文件被多次写入或截断(如FileMode.Create) - 多个线程同时调用同一实例的
Execute(),引发ObjectDisposedException或文件锁冲突 - 误把
WithEncoding()当成全局设置,结果后续其他FileOp实例也「被影响」(其实是静态字段滥用)
解决方式很简单:让 FileOp 不可重入。可在 Execute() 开头加标记:
private bool _executed;
public void Execute()
{
if (_executed) throw new InvalidOperationException("This FileOp instance has already been executed.");
_executed = true;
// ... rest
}
更彻底的做法是让 Execute() 返回 void 且自动使实例失效 —— 这比试图让它“可重用”更符合 Fluent 的语义直觉。
真正需要复用逻辑时,应该提取配置类或工厂方法,而不是复用 Fluent 实例。









