有些程序会使用 Path.GetFullPath() 规范化路径,再通过 == 或 StartsWith() 判断两个目录是否相同或是否存在包含关系:
var expected = Path.GetFullPath(Path.Combine(root, name));
var matched = expected.StartsWith(existingFolder);
大部分情况下它都能正常工作,Path.GetFullPath() 通常会保留传入路径的大小写,后续字符串比较也能通过。我们的代码跑了很多年,似乎没什么问题。
直到遇到了~。
假设磁盘上已有目录:
D:\data
程序使用另一种大小写构造根目录,并枚举磁盘中已经存在的目录:
using System;
using System.IO;
var root = @"D:\Data";
var name = "demo~cache";
var expected = Path.GetFullPath(Path.Combine(root, name));
var existing = Directory.GetDirectories(root, name)[0];
Console.WriteLine($"expected: {expected}");
Console.WriteLine($"existing: {existing}");
Console.WriteLine($"Equal: {expected == existing}");
Console.WriteLine($"StartsWith: {expected.StartsWith(existing)}");
在父目录实际以 data 保存时,可能得到:
expected: D:\data\demo~cache
existing: D:\Data\demo~cache
Equal: False
StartsWith: False
两个字符串指向同一个 Windows 目录,但 Path.GetFullPath() 的结果使用了磁盘保存的 data,另一个路径仍使用程序传入的 Data。默认的 == 和 StartsWith() 区分大小写,因此判断失败。
原因是 .NET 8 在规范化路径时,只要结果包含 ~,就会尝试调用 Windows GetLongPathNameW 展开短路径,并使用磁盘中的真实目录拼写。
.NET 8 相关实现可参考 PathHelper.Windows.cs。