【问题标题】:Why does Path.Combine not properly concatenate filenames that start with Path.DirectorySeparatorChar?为什么 Path.Combine 不能正确连接以 Path.DirectorySeparatorChar 开头的文件名?
【发布时间】:2010-09-08 08:40:28
【问题描述】:

从 Visual Studio 中的即时窗口

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

看来它们应该是一样的。

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

【问题讨论】:

  • @Joe,愚蠢的是对的!另外,我必须指出 the equivalent function 在 Node.JS 中工作得很好......在微软摇头......
  • @zwcloud 对于 .NET Core/Standard,Path.Combine() 主要用于向后兼容(与现有行为)。你最好使用Path.Join(): "与Combine方法不同,Join方法不会尝试对返回的路径进行root。(也就是说,如果path2是绝对路径,Join方法不会丢弃path1并像 Combine 方法一样返回 path2。)"

标签: c# .net file


【解决方案1】:

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

System.IO.Path.Combine

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

Here's the actual Combine method 来自 .NET 源。你可以看到它调用了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()。

【讨论】:

  • 查看反汇编代码(查看我的帖子),您在某种程度上是对的。
  • 我猜它的工作方式是允许轻松访问“当前工作目录”算法。
  • 它似乎像在命令行中执行一系列cd (component) 一样工作。对我来说听起来很合理。
  • 我使用这个修剪来获得想要的效果字符串 strFilePath = Path.Combine(basePath, otherPath.TrimStart(new char[] {'\\', '/' }) );
  • 为了安全起见,我确实将我的工作代码更改为Path.Combine,但后来它坏了..太愚蠢了:)
【解决方案2】:

我想解决这个问题:

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

我也认为这个字符串处理必须手动完成很烦人,我很想知道这背后的原因。

【讨论】:

  • 我刚刚使用了 string.Concat(path1, path2) 并且它适用于我的情况。
【解决方案3】:

这是来自 .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;
}

【讨论】:

    【解决方案4】:

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

    【讨论】:

    • 它符合规范,但也不是我所期望的。
    • @Jake 这并没有避免错误修复;那是几个人长时间而努力地思考如何做某事,然后坚持他们同意的任何事情。另外,请注意 .Net 框架(包含 Path.Combine 的库)和 C# 语言之间的区别。
    【解决方案5】:

    来自MSDN

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

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

    【讨论】:

      【解决方案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 冲突。

      编辑:添加空参数检查

      【讨论】:

        【解决方案7】:

        这段代码应该可以解决问题:

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

        【讨论】:

          【解决方案8】:

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

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

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

          【讨论】:

          • 我认为应该解释正斜杠。另外,这与 .NET 有什么关系?
          【解决方案9】:

          原因:

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

          解决方案:

          只需从您的第二个路径(/SecondPathSecondPath)中删除前导斜杠 /,它就可以正常工作。

          【讨论】:

            【解决方案10】:

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

            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
            

            真正的问题是:为什么以"\" 开头的路径被认为是“rooted”?这对我来说也是新的,但是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
            

            【讨论】:

              【解决方案11】:

              我使用聚合函数来强制路径组合如下:

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

              【讨论】:

              • 这个可以插入任何问题所在的地方。附带说明:多么烦人的问题!
              【解决方案12】:

              如果你想在不丢失任何路径的情况下合并两条路径,你可以使用这个:

              ?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 是否以 / 开头,如果是,则返回没有第一个字符的 Path2。否则,返回完整的 Path2。

              【讨论】:

              • Path.IsRooted() 调用替换== @"\" 检查可能更安全,因为"\" 不是唯一需要考虑的字符。
              • 你可以使用 .Trim('\') 代替
              【解决方案13】:

              去掉Path.Combine的第二个参数(path2)中的起始斜杠('\')。

              【讨论】:

              • 问题不是在问这个。
              【解决方案14】:

              这两种方法应该可以避免您意外连接两个都有分隔符的字符串。

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

              【讨论】:

                【解决方案15】:

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

                【讨论】:

                  【解决方案16】:

                  正如 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
                  

                  【讨论】:

                    猜你喜欢
                    • 2010-12-04
                    • 2010-09-25
                    • 2017-10-28
                    • 2020-07-16
                    • 2020-12-09
                    • 2021-07-10
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多