最稳方案是用 Path.GetFileName 和 Path.GetExtension 获取文件名与扩展名,而非手动 Split 或正则;它们跨平台、防注入、正确处理隐藏文件、无扩展名文件及带点目录名等边界情况。

用 Path.GetFileName 和 Path.GetExtension 拿文件名和后缀最稳
直接上结论:别自己 Split 或正则,Path 类是专干这事的,跨平台、防路径注入、处理各种边界(比如 "C:\a.b\c" 或 "/home/user/.bashrc")都可靠。
常见错误是手动找最后一个 '.' 或 '\' —— 一旦遇到隐藏文件(.gitignore)、无扩展名文件(Dockerfile)、带点的目录名("my.app/config.json"),结果就错。
实操建议:
-
Path.GetFileName("logs/error.log")→"error.log"(不含路径) -
Path.GetFileNameWithoutExtension("logs/error.log")→"error"(不含扩展名) -
Path.GetExtension("logs/error.log")→".log"(含点,注意不是空字符串) - 对无扩展名文件如
"README",Path.GetExtension返回""(空字符串),不是null
Path.GetExtension 返回空字符串?先确认是不是真没后缀
很多人看到 Path.GetExtension("appsettings") 返回 "" 就以为出错了,其实这是正确行为——它只认最后那个 '.' 后面的部分,且要求前面至少有一个非点字符。像 "config."、"a..b" 都会返回 "",因为不符合“合法扩展名”定义。
如果你需要兼容“点结尾”或“多点分隔”的场景(比如某些配置文件约定),得自己加判断,Path 类不负责猜意图。
实操建议:
- 检查输入是否为
null或空字符串,Path.GetExtension会抛ArgumentException - 如果业务上把
"file."当作有扩展名,就别用Path.GetExtension,改用lastIndexOf('.') != -1+ 子串截取 -
Path.GetExtension不区分大小写,但返回值大小写跟原字符串一致("FILE.TXT"→".TXT")
在 .NET Core / .NET 5+ 中要注意路径分隔符兼容性
Path 类内部会根据运行时自动适配 / 或 ,所以 Path.GetFileName("/usr/local/bin/python3") 在 Windows 上也返回 "python3",不用手动替换分隔符。
但容易被忽略的是:如果你拼接了用户输入的路径(比如 HTTP 请求里的 filename 参数),而没做规范化,Path.GetFileName 可能被绕过——例如传入 "../../etc/passwd",它仍会返回 "passwd",但这不代表路径安全。
实操建议:
- 仅用
Path.GetFileName提取名字,不做路径拼接;拼接前务必用Path.GetFullPath+ 白名单校验根目录 - 避免把
Path.GetFileName结果直接用于磁盘写入,尤其配合Directory.GetCurrentDirectory()时 - 在 Linux 容器里跑 Windows 编译的程序?放心,
Path行为由运行时决定,不是编译目标平台
性能敏感场景下,Span<char></char> 版本不值得换
有人看到 .NET 6+ 加了 Path.GetFileName(ReadOnlySpan<char>)</char> 就想优化,其实没必要。普通字符串版在绝大多数场景下没有可测出的性能差异,而且 Span 版本只省了一次字符串分配,调用栈深、IO 占大头时这点开销根本看不见。
真正卡性能的地方通常是反复解析同一路径、或在 tight loop 里调用——这时候应该缓存结果,而不是换 API。
实操建议:
- 除非你用
Span处理大量原始字节流(比如从网络包里切路径),否则坚持用string版本,代码更直白、调试更方便 - 如果真要微优化,优先考虑把
Path.GetFileName结果缓存到字段或局部变量,避免重复调用 -
Path类所有方法都是静态的、无状态的,线程安全,不用加锁
Path.GetFileName 当路径净化工具用,它只拆名字,不验证合法性,也不防遍历攻击。










