diffplex是c#中成熟、活跃、api简洁的纯内存行级文本比对库,返回结构化diffresult;需统一编码、换行符并预处理业务规则,不适用于二进制或超大文件。

用 DiffPlex 做行级文本比对最省事
直接上结论:别手写 diff 算法,DiffPlex 是目前 C# 生态里最成熟、维护活跃、API 干净的开源库,它不依赖 Git,纯内存比对,适合嵌入工具或 UI 展示。
常见错误是试图用 string.Equals 或逐行 SequenceEqual 判断“是否相同”——这只能回答 yes/no,没法告诉你哪几行删了、哪几行改了、插入在哪。而 DiffPlex 返回的是结构化的 DiffResult,含 LineType(Added/Deleted/Modified/Unchanged)、行号、原始内容片段。
- 安装:
dotnet add package DiffPlex - 基础用法:传入两个
string,调用DiffBuilder.BuildDiffModel,结果里遍历DiffResult.Lines - 注意编码:如果文件含 BOM 或 UTF-8/UTF-16 混用,先统一用
File.ReadAllText(path, Encoding.UTF8)读取,避免乱码导致误判差异 - 性能提示:单次比对百万行文本在普通机器上约 200–500ms;若需高频比对(如编辑器实时预览),建议加缓存或节流,别每次 keystroke 都跑全量 diff
处理二进制文件或大文件时不能只靠 DiffPlex
DiffPlex 只处理字符串,对图片、PDF、编译后的 DLL 等二进制文件完全无效;对超大文本(比如 500MB 日志)也会因内存爆掉而抛 OutOfMemoryException。
真实场景中,你得先判断文件类型再分流:
- 用
Path.GetExtension(path)快速识别扩展名,对.png、.pdf、.exe直接跳过文本 diff,改用哈希比对(SHA256.Create().ComputeHash(stream)) - 对超大文本,别一次性
ReadAllText;改用StreamReader分块读取前 N 行 + 后 M 行,再用DiffPlex比对“首尾摘要”,中间用行数差+哈希跳过(类似 git 的 sparse diff 思路) - 注意换行符:Windows(
)、Linux()、macOS()混用会导致DiffPlex把同一逻辑行拆成多行;建议预处理统一为再比对
想模拟 git diff --no-index 的输出格式?自己拼字符串就行
Git 的 diff 输出(带 @@ -1,3 +1,4 @@ 头和 +/- 前缀)不是 DiffPlex 默认提供的,但它给足了原始数据,拼起来不难。
关键点在于理解 DiffResult.Lines 的顺序和 LineNumber 含义:它按“合并后视图”排列,每行带 LineType 和 LineNumber(对应左/右文件原始行号),但没直接给你 hunk 起始偏移——得自己扫描连续的 Added/Deleted 块来构造 @@ 行。
- 遍历
Lines,收集连续同类型操作的起始位置、长度、原始行号 -
@@ -A,B +C,D @@中的A是左文件起始行,B是左文件行数(含 unchanged),C和D同理对应右文件 - 别硬套 git 的上下文行数(默认 3 行);根据你的 UI 或日志需求调整,比如 CLI 工具设为 1,IDE 插件设为 5
- 示例片段:
line.LineType == LineType.Added ? $"+{line.Text}" : $"-{line.Text}",但注意空行和制表符要原样保留,否则影响可读性
忽略空格、大小写或特定注释时,预处理比改算法更可靠
有人想改 DiffPlex 源码支持“忽略空格”,其实没必要——它的输入是字符串,你在传入前做标准化,效果一样,还避免引入不可控行为。
比如比对代码配置文件时,常要忽略前后空格、空白行、行尾注释(// ... 或 # ...),这些规则高度业务相关,硬塞进通用 diff 库反而僵化。
- 预处理函数示例:
text.Replace(" ", "").Replace(" ", "").Replace(" ", " ")(慎用,可能破坏缩进语义) - 更安全做法:用正则清理注释(
Regex.Replace(text, @"//.*|#.*)", "")),再 trim 每行两端空格,但保留中间空格(防破坏 CSV 或日志字段分隔) - 大小写敏感与否,由你决定传入前调用
.ToLowerInvariant();但注意:一旦转小写,定位原始行号时得同步记录原始行,否则点击跳转会错位 - 坑点:UTF-8 中的全角空格(
)、零宽空格(u200B)不会被Trim()清掉,需额外Regex.Replace(text, "[\u200B-\u200F\u202A-\u202E]", "")
diff 的难点从来不在算法本身,而在你怎么定义“什么算不同”。行号对齐、编码一致、换行符归一、业务规则预处理——这些环节出一点偏差,结果就不可信。别指望一个库解决所有问题,重点是把边界条件想清楚再动手。










