为什么Path.Combine不正确连接以Path.DirectorySeparatorChar开头的文件名?

从Visual Studio中的即时窗口

> Path.Combine(@"C:\x", "y") "C:\\x\\y" > Path.Combine(@"C:\x", @"\y") "\\y" 

看来他们应该是一样的。

旧的FileSystemObject.BuildPath()不能这样工作…

这是一个哲学问题(也许只有微软才能真正回答),因为它正在做文档所说的。

System.IO.Path.Combine

“如果path2包含绝对path,则此方法返回path2。”

以下是 .NET源代码中的实际组合方法 。 你可以看到它调用了CombineNoChecks ,然后在path2上调用IsPathRooted ,如果是这样,返回该path。

我不知道理由是什么。 我想解决的办法是从第二条path的开始剥离(或修剪)DirectorySeparatorChar; 也许编写自己的Combine方法,然后调用Path.Combine()。

这是来自.NET Reflector for Path.Combine方法的反汇编代码。 检查IsPathRooted函数。 如果第二个path是根(以DirectorySeparatorChar开头),则返回第二个path。

 public static string Combine(string path1, string path2) { if ((path1 == null) || (path2 == null)) { throw new ArgumentNullException((path1 == null) ? "path1" : "path2"); } CheckInvalidPathChars(path1); CheckInvalidPathChars(path2); if (path2.Length == 0) { return path1; } if (path1.Length == 0) { return path2; } if (IsPathRooted(path2)) { return path2; } char ch = path1[path1.Length - 1]; if (((ch != DirectorySeparatorChar) && (ch != AltDirectorySeparatorChar)) && (ch != VolumeSeparatorChar)) { return (path1 + DirectorySeparatorChar + path2); } return (path1 + path2); } public static bool IsPathRooted(string path) { if (path != null) { CheckInvalidPathChars(path); int length = path.Length; if ( ( (length >= 1) && ( (path[0] == DirectorySeparatorChar) || (path[0] == AltDirectorySeparatorChar) ) ) || ((length >= 2) && (path[1] == VolumeSeparatorChar)) ) { return true; } } return false; } 

在我看来,这是一个错误。 问题是有两种不同types的“绝对”path。 path“d:\ mydir \ myfile.txt”是绝对path,“\ mydir \ myfile.txt”也被认为是“绝对”,即使它缺less驱动器号。 在我看来,正确的行为将是当第二个path以目录分隔符开始(而不是UNCpath)时,从第一个path预先安装驱动器号。 我会build议编写自己的帮手包装function,如果你需要它有你想要的行为。

好的,已经有很长的答案了,这里是我的;-)

我想解决这个问题:

 string sample1 = "configuration/config.xml"; string sample2 = "/configuration/config.xml"; string sample3 = "\\configuration/config.xml"; string dir1 = "c:\\temp"; string dir2 = "c:\\temp\\"; string dir3 = "c:\\temp/"; string path1 = PathCombine(dir1, sample1); string path2 = PathCombine(dir1, sample2); string path3 = PathCombine(dir1, sample3); string path4 = PathCombine(dir2, sample1); string path5 = PathCombine(dir2, sample2); string path6 = PathCombine(dir2, sample3); string path7 = PathCombine(dir3, sample1); string path8 = PathCombine(dir3, sample2); string path9 = PathCombine(dir3, sample3); 

当然,所有的path1-9最后都应该包含一个等效的string。 这里是我提出的PathCombine方法:

 private string PathCombine(string path1, string path2) { if (Path.IsPathRooted(path2)) { path2 = path2.TrimStart(Path.DirectorySeparatorChar); path2 = path2.TrimStart(Path.AltDirectorySeparatorChar); } return Path.Combine(path1, path2); } 

我也认为这是很烦人的,这个string处理必须手动完成,我会对此背后的原因感兴趣。

不知道实际的细节,我的猜测是它试图join,就像你可能会join相对URI一样。 例如:

 urljoin('/some/abs/path', '../other') = '/some/abs/other' 

这意味着当你使用前面的斜线join一个path时,你实际上将一个基础连接到另一个基础上,在这种情况下,第二个基础优先。

来自MSDN:

如果指定的path之一是零长度的string,则此方法返回另一个path。 如果path2包含绝对path,则此方法返回path2。

在你的例子中,path2是绝对的。

这个代码应该做的伎俩:

  string strFinalPath = string.Empty; string normalizedFirstPath = Path1.TrimEnd(new char[] { '\\' }); string normalizedSecondPath = Path2.TrimStart(new char[] { '\\' }); strFinalPath = Path.Combine(normalizedFirstPath, normalizedSecondPath); return strFinalPath; 

根据Christian Graus在他的“我讨厌微软的事情”博客中的build议,标题为“ Path.Combine本质上是无用的 ”,这里是我的解决scheme:

 public static class Pathy { public static string Combine(string path1, string path2) { if (path1 == null) return path2 else if (path2 == null) return path1 else return path1.Trim().TrimEnd(System.IO.Path.DirectorySeparatorChar) + System.IO.Path.DirectorySeparatorChar + path2.Trim().TrimStart(System.IO.Path.DirectorySeparatorChar); } public static string Combine(string path1, string path2, string path3) { return Combine(Combine(path1, path2), path3); } } 

有人build议命名空间应该碰撞,…我用Pathy ,轻微,并避免与System.IO.Path命名空间冲突。

编辑 :添加空参数检查

这意味着“当前驱动器的根目录”。 在你的例子中,它表示当前驱动器根目录下的“test”文件夹。 所以,这可以等同于“c:\ test”

如果你想结合两个path而不丢失任何path,你可以使用这个:

 ?Path.Combine(@"C:\test", @"\test".Substring(0, 1) == @"\" ? @"\test".Substring(1, @"\test".Length - 1) : @"\test"); 

或者与variables:

 string Path1 = @"C:\Test"; string Path2 = @"\test"; string FullPath = Path.Combine(Path1, Path2.Substring(0, 1) == @"\" ? Path2.Substring(1, Path2.Length - 1) : Path2); 

这两种情况都返回“C:\ test \ test”。

首先,我评估Path2是否以/开头,如果是,则返回没有第一个字符的Path2。 否则,返回完整path2。

这在某种程度上考虑了(相对)path通常是如何处理的:

 string GetFullPath(string path) { string baseDir = @"C:\Users\Foo.Bar"; return Path.Combine(baseDir, path); } // get full path for RELATIVE file path GetFullPath("file.txt"); // = C:\Users\Foo.Bar\file.txt // get full path for ROOTED file path GetFullPath(@"C:\Temp\file.txt"); // = C:\Temp\file.txt 

真正的问题是,为什么以"\"开头的path被认为是“根源”。 这对我来说是新的,但它在Windows上是这样工作的 :

 new FileInfo("\windows"); // FullName = C:\Windows, Exists = True new FileInfo("windows"); // FullName = C:\Users\Foo.Bar\Windows, Exists = False 

这两种方法可以避免意外地join两个string,这两个string中都有分隔符。

  public static string Combine(string x, string y, char delimiter) { return $"{ x.TrimEnd(delimiter) }{ delimiter }{ y.TrimStart(delimiter) }"; } public static string Combine(string[] xs, char delimiter) { if (xs.Length < 1) return string.Empty; if (xs.Length == 1) return xs[0]; var x = Combine(xs[0], xs[1], delimiter); if (xs.Length == 2) return x; var ys = new List<string>(); ys.Add(x); ys.AddRange(xs.Skip(2).ToList()); return Combine(ys.ToArray(), delimiter); }