Directory.EnumerateDirectories 会因未检测符号链接循环而无限递归;需用 File.GetAttributes 判断 ReparsePoint,再通过 File.ReadLinkTarget 获取目标并比对归一化绝对路径,或用 HashSet 记录 Path.GetFullPath 结果防重。

为什么 Directory.EnumerateDirectories 会陷入无限循环
Windows 上的符号链接(Symbolic Link)或 Junction 点,本质是文件系统级的重定向。当 Directory.EnumerateDirectories 遇到指向父目录或自身路径的符号链接时,它默认不检测循环,而是照常递归进入——结果就是路径不断“绕圈”,最终抛出 System.IO.IOException: 目录名称无效 或直接栈溢出。
这不是 .NET 的 bug,而是设计使然:该 API 不解析链接目标,也不维护访问路径历史。要安全遍历,必须自己拦截并判重。
用 GetLinkTarget 判断是否为符号链接并跳过
从 .NET Core 2.1 / .NET 5+ 开始,File.GetAttributes 可识别 FileAttributes.ReparsePoint,再结合 File.ReadLinkTarget 获取真实目标路径,就能区分普通目录和符号链接:
var attr = File.GetAttributes(path);
if ((attr & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint)
{
try
{
var target = File.ReadLinkTarget(path);
// 若 target 是绝对路径且在遍历根目录内,大概率是循环风险点
if (target.IsParentOf(rootPath) || target.Equals(rootPath, StringComparison.OrdinalIgnoreCase))
{
return; // 跳过该链接
}
}
catch (IOException) { /* 忽略读取失败的链接 */ }
}-
File.ReadLinkTarget在 .NET Framework 中不可用,需降级用 P/Invoke 调用CreateFile+DeviceIoControl - 判断“是否在根目录内”建议用
Path.GetFullPath(target).StartsWith(Path.GetFullPath(rootPath), StringComparison.Ordinal),避免大小写或 UNC 路径误判 - 不要仅靠路径字符串相等判断循环——比如
C:\a\link → ..\b和C:\a\b逻辑等价但字符串不同
用 HashSet 记录已访问的真实路径
最稳妥的方式不是跳过所有链接,而是记录每个目录的**真实物理路径**(即 Directory.GetDirectoryRoot + Path.GetFullPath 归一化后的结果),遇到重复就终止递归分支:
var visited = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
void SafeWalk(string path)
{
var realPath = Path.GetFullPath(path);
if (!visited.Add(realPath)) return; // 已访问过,退出
<pre class="brush:php;toolbar:false;">foreach (var sub in Directory.EnumerateDirectories(path))
{
SafeWalk(sub);
}}
- 必须用
Path.GetFullPath,否则C:\a\..\b和C:\b会被视为两个路径 -
StringComparer.OrdinalIgnoreCase是必须的——NTFS 不区分大小写,但GetFullPath可能返回任意大小写形式 - 这个方案对硬链接(Hard Link)也有效,但硬链接在目录层面极少出现,主要影响文件
用 PowerShell 或 cmd /c dir /AL 的替代思路
如果项目允许调用外部命令,dir /AL /S /B(列出所有链接)或 Get-ChildItem -Attributes ReparsePoint 能快速筛出可疑项,提前排除后再用 C# 遍历。但这增加了环境依赖和权限要求,且无法嵌入实时路径决策逻辑。
真正难处理的不是“怎么发现链接”,而是“如何定义循环”——比如一个链接指向网络共享路径,而该共享又映射回本机某目录,这种跨协议、跨主机的间接循环,单靠本地路径哈希无法识别。这时候需要更上层的业务约束,比如限定最大深度、限制链接跳转次数,或由管理员白名单控制可访问的链接目标范围。










