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


181

从Visual Studio 的立即窗口中:

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

似乎它们应该是相同的。

旧的FileSystemObject.BuildPath()无法以这种方式工作...



@乔,傻是对的!另外,我必须指出,等效功能在Node.JS中也正常工作……在Microsoft摇头……
NH。

2
@zwcloud对于.NET Core / Standard,Path.Combine()主要是为了向后兼容(与现有行为)。您最好使用Path.Join()以下方法
Stajs

Answers:


201

这是一个哲学问题(也许只有Microsoft才能真正回答),因为它确实在按照文档中的说明进行操作。

System.IO.Path.Combine

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

这是来自.NET源的实际Combine方法。您可以看到它调用CombineNoChecks,然后在path2上调用IsPathRooted,如果是,则返回该路径:

public static String Combine(String path1, String path2) {
    if (path1==null || path2==null)
        throw new ArgumentNullException((path1==null) ? "path1" : "path2");
    Contract.EndContractBlock();
    CheckInvalidPathChars(path1);
    CheckInvalidPathChars(path2);

    return CombineNoChecks(path1, path2);
}

internal static string CombineNoChecks(string path1, string 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 + DirectorySeparatorCharAsString + path2;
    return path1 + path2;
}

我不知道这是什么理由。我猜解决方案是从第二个路径的开头剥离(或修剪)DirectorySeparatorChar;也许编写自己的Combine方法来执行该操作,然后调用Path.Combine()。


查看反汇编的代码(请查看我的文章),您在某种程度上是正确的。
Gulzar Nazim

7
我猜想它可以通过这种方式轻松访问“当前工作目录”算法。
BCS

看起来就像cd (component)从命令行执行一系列操作一样。对我来说听起来很合理。
Adrian Ratnapala 2014年

11
我使用这种修饰来获得所需的效果字符串strFilePath = Path.Combine(basePath,otherPath.TrimStart(new char [] {'\\','/'})));
马修·洛克

3
我确实将我的工作代码更改为Path.Combine只是为了安全起见,但是随后它

23

这是.NET Reflector中用于Path.Combine方法的反汇编代码。检查IsPathRooted函数。如果第二条路径是根目录(以DirectorySeparatorChar开头),则按原样返回第二条路径。

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;
}

23

我想解决这个问题:

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);

当然,所有路径1-9末尾都应包含一个等效字符串。这是我想出的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);
}

我还认为必须手动完成此字符串处理是很烦人的,并且我会对这背后的原因感兴趣。


19

我认为这是一个错误。问题是存在两种不同类型的“绝对”路径。路径“ d:\ mydir \ myfile.txt”是绝对路径,路径“ \ mydir \ myfile.txt”也被视为“绝对路径”,即使它缺少驱动器号。我认为正确的行为是,当第二个路径以目录分隔符开头(而不是UNC路径)时,在第一个路径前添加驱动器号。我建议编写自己的辅助包装函数,如果需要的话,该函数具有所需的行为。


7
它符合规格,但这也不是我所期望的。
dthrasher

@Jake不能避免错误修正;那是几个人认真思考如何做某事,然后坚持他们所同意的一切。另外,请注意.Net框架(包含的库Path.Combine)与C#语言之间的区别。
Grault

9

MSDN

如果指定的路径之一是长度为零的字符串,则此方法返回另一个路径。如果path2包含绝对路径,则此方法返回path2。

在您的示例中,path2是绝对的。


7

克里斯蒂安·格劳斯Christian Graus)在他的“我讨厌微软的事情”博客“ Path.Combine本质上无用。 ”的建议之后,以下是我的解决方案:

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);
    }
}

有人建议命名空间应该发生冲突,...我Pathy略微同意,以避免与冲突System.IO.Path

编辑:添加了空参数检查


4

这段代码可以解决这个问题:

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

4

我不知道实际的细节,我想它会像您可能会加入相对URI一样尝试加入。例如:

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

这意味着当您将路径与前面的斜杠连接时,实际上是将一个基础与另一个基础连接,在这种情况下,第二个将优先。


我认为斜线应该解释。另外,这与.NET有什么关系?
彼得·莫滕森

3

原因:

您的第二个URL被认为是绝对路径,Combine如果最后一个路径是绝对路径,则该方法只会返回最后一个路径。

解决方案:只需删除/第二个路径(/SecondPathSecondPath)的开始斜杠。然后按您的要求工作。


3

考虑到通常如何处理(相对)路径,这实际上在某种意义上是有意义的:

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

真正的问题是:为什么以开头的路径"\"被认为是“有根的”?这对我来说也是新的,但是在Windows上可以这样工作

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

1

如果要合并这两个路径而不丢失任何路径,可以使用以下方法:

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

或带有变量:

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

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

首先,我评估Path2是否以/开头,如果为true,则返回不带第一个字符的Path2。否则,返回完整的Path2。


1
== @"\"Path.IsRooted()电话代替支票可能更安全,因为"\"它不是唯一要考虑的字符。
rumblefx0

0

这两种方法可以避免意外连接两个都包含定界符的字符串。

    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);
    }

0

\表示“当前驱动器的根目录”。在您的示例中,它表示当前驱动器根目录中的“ test”文件夹。因此,这可以等于“ c:\ test”。



0

我使用了聚合函数来强制路径合并,如下所示:

public class MyPath    
{
    public static string ForceCombine(params string[] paths)
    {
        return paths.Aggregate((x, y) => Path.Combine(x, y.TrimStart('\\')));
    }
}

0

正如Ryan所提到的,它完全按照文档中的说明进行操作。

根据DOS时间,可以区分当前磁盘和当前路径。 \是根路径,但用于CURRENT DISK。

对于每个“ 磁盘 ”,都有一个单独的“ 当前路径 ”。如果使用来更改磁盘cd D:,则不要将当前路径更改为D:\,而是更改为:“ D:\ whatever \ was \ the \ last \ path \ accessed \ on \ this \ disk” ...

因此,在Windows中,文字的@"\x"含义是:“ CURRENTDISK:\ x”。因此Path.Combine(@"C:\x", @"\y"),它的第二个参数是一个根路径,而不是一个相对路径,尽管不在一个已知的磁盘中……而且由于不知道可能是“当前磁盘”,因此python返回"\\y"

>cd C:
>cd \mydironC\apath
>cd D:
>cd \mydironD\bpath
>cd C:
>cd
>C:\mydironC\apath
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.