不能直接硬编码解析因不同格式逻辑差异大,易导致维护难、测试覆盖不足;应定义统一泛型接口ifileparser,含canhandle、preferredencoding、同步/异步parse方法,并通过插件目录扫描+assemblyloadcontext隔离加载,流式处理防内存爆炸与编码错误。

为什么不能直接用 File.ReadAllLines 或 StreamReader 硬编码解析?
因为不同格式(CSV、JSON、XML、自定义分隔文本、固定宽字段文件)的解析逻辑差异大:字段提取方式、编码处理、错误容忍策略、行首/尾空白处理、注释跳过、嵌套结构支持等全都不一样。硬编码会导致每加一种格式就要改主流程,测试难覆盖,上线后改一个解析器可能牵连全部。
如何定义统一的解析接口和插件契约?
核心是抽象出输入、输出和生命周期三要素。推荐定义一个泛型接口:
public interface IFileParser<T>
{
bool CanHandle(string filePath);
Encoding? PreferredEncoding { get; }
IEnumerable<T> Parse(Stream stream);
Task<IEnumerable<T>> ParseAsync(Stream stream);
}
关键点:
-
CanHandle必须轻量——只看扩展名或前几百字节(如检查"{"判断 JSON),不能打开整个文件 -
PreferredEncoding让调用方提前选择正确编码,避免StreamReader自动探测失败(尤其中文 GBK/UTF-8 混用时) - 同步/异步双方法,方便适配不同场景;但不要在同步方法里用
.GetAwaiter().GetResult(),会死锁 - 返回
IEnumerable<t></t>而非List<t></t>,支持流式处理大文件(如逐行解析 10GB 日志)
插件怎么自动发现和加载?别碰 Assembly.LoadFrom
直接加载 DLL 容易引发版本冲突、类型重复、卸载困难。更稳妥的做法是约定插件目录 + 接口实现扫描:
- 插件 DLL 放在
./parsers/目录下,命名含Parser(如CsvParser.dll) - 主程序启动时用
AssemblyLoadContext.Default.Assemblies扫描已加载程序集,或用AssemblyLoadContext.GetLoadContext(assembly).Assemblies隔离加载 - 用
Assembly.GetTypes().Where(t => t.IsClass && !t.IsAbstract && typeof(IFileParser).IsAssignableFrom(t))找实现类 - 通过
Activator.CreateInstance创建实例,而非反射调用构造函数——避免传参错位
注意:.NET 6+ 推荐用 AssemblyDependencyResolver 处理插件依赖,否则插件引用了不同版本的 Newtonsoft.Json 会炸。
实际解析时怎么避免内存爆炸和编码翻车?
大文件和乱码是两类高频崩点:
- 永远用
Stream入参,而不是string路径——路径由上层决定是否缓存/重试,解析器只管读 - 对文本类格式(CSV/TSV),用
StreamReader(stream, encoding, detectEncodingFromByteOrderMarks: true),并捕获DecoderFallbackException做降级(比如 fallback 到Encoding.UTF8并跳过坏字节) - 对 JSON/XML,用
JsonSerializer.DeserializeAsyncEnumerable或XmlReader流式反序列化,禁用JsonConvert.DeserializeObject<list>></list>全量加载 - 每个插件必须实现超时控制——比如
ParseAsync内部用CancellationToken检查,防止某行卡死整个管道
最常被忽略的是:插件初始化阶段不做 IO,所有耗时操作延后到 Parse 调用时才触发。否则热加载插件时,还没用就先报错。










