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

时间:2008-09-09 23:05:07

标签: c# .net file

从Visual Studio中的立即窗口

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

它们似乎都应该是一样的。

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

16 个答案:

答案 0 :(得分:185)

这是一个哲学问题(可能只有微软可以真正回答),因为它正在完成文档所说的内容。

System.IO.Path.Combine

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

来自.NET源代码的

Here's the actual Combine method。您可以看到它调用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()。

答案 1 :(得分:22)

这是来自.NET Reflector的Path.Combine方法的反汇编代码。检查IsPathRooted功能。如果第二个路径是root(以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;
}

答案 2 :(得分:20)

我想解决这个问题:

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

我还认为这个字符串处理必须手动完成并且我对它背后的原因感兴趣是非常烦人的。

答案 3 :(得分:15)

在我看来这是一个错误。问题是有两种不同类型的“绝对”路径。路径“d:\ mydir \ myfile.txt”是绝对的,路径“\ mydir \ myfile.txt”也被认为是“绝对的”,即使它缺少驱动器号。在我看来,正确的行为是当第二条路径以目录分隔符开始时(并且不是UNC路径),从第一条路径前面加上驱动器号。我建议您编写自己的帮助包装函数,如果需要,可以使用您想要的行为。

答案 4 :(得分:7)

来自MSDN

  

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

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

答案 5 :(得分:6)

在他的“我讨厌微软的事情”博客中提到Christian Graus的建议,标题为“Path.Combine is essentially useless.”,这是我的解决方案:

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发生命名空间冲突。

修改:添加了空参数检查

答案 6 :(得分:5)

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

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

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

答案 7 :(得分:3)

这段代码可以解决问题:

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

答案 8 :(得分:3)

<强>原因:

您的第二个URL被视为绝对路径,如果最后一个路径是绝对路径,Combine方法将仅返回最后一个路径。

解决方案:只需删除第二条路径(//SecondPath)的起始斜杠SecondPath即可。然后它就像你排除的那样工作。

答案 9 :(得分:2)

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

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

真正的问题是:为什么以"\"开头的路径被视为“根”?这对我来说也是新的,但是it works that way on Windows

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

答案 10 :(得分:0)

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

答案 11 :(得分:0)

如果要在不丢失任何路径的情况下组合两个路径,可以使用:

?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.Substring(0, 1) == @"\" ? Path2.Substring(1, Path2.Length - 1) : Path2);

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

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

答案 12 :(得分: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);
    }

答案 13 :(得分:0)

删除Path.Combine的第二个参数(path2)中的开始斜杠('\')。

答案 14 :(得分:0)

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

    var obj1 = JSON.parse(payload);

    axios.request({
        url: inUrl,
        method: "POST",
        auth: {
            username: auth,
            password: pass
        },
        headers: { 'content-type': 'application/json' },
        data: {
            "success": inState,
            "fails": inCount,
            obj1
        }
    }).then(res => {
        //console.log(res);
        console.log("bucket commopn response: "+ res.status);
        return "success";
    }).catch(error => {
        console.log(error);
        console.log("bucket error reponse: " + error.response.status);
        return "error";
    });

答案 15 :(得分:0)

正如瑞安(Ryan)所提到的,它确实按照文档中的说明进行操作。

根据DOS时间,区分当前磁盘和当前路径。 \是根路径,但对于当前磁盘。

对于每个“ 磁盘”,都有一个单独的“ 当前路径”。 如果使用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