【问题标题】:Correct way to calculate local path from relative URI从相对 URI 计算本地路径的正确方法
【发布时间】:2026-02-24 16:15:01
【问题描述】:

似乎没有标准方法可以从相对 URI (this property is valid only for absolute URIs) 计算 LocalPath,以便与 Path.Combine 结合使用,例如将其与文件掩码 (*.分机)。问题是MakeRelativeUri 产生类似于my%20folder/ 的东西,而不是my folder\

这是我找到的解决方法:

Module Module1
  Sub Main()
    Dim path1 As String = "C:\my folder\"
    Dim path2 As String = "C:\"
    MsgBox(GetPathDiff(path1, path2)) 'outputs "my folder\" (without quotes)
  End Sub

  Private Function GetPathDiff(path1 As String, path2 As String) As String
    Dim uri1 As New Uri(path1)
    Dim uri2 As New Uri(path2)
    Dim uri3 As Uri = uri2.MakeRelativeUri(uri1)
    Return Uri.UnescapeDataString(uri3.OriginalString).Replace("/", "\")
  End Function
End Module

我觉得这是一种相当笨拙的方法,并且可能有一些我还没有偶然发现的隐藏石头,即这种方法对于不同的用例并不是 100% 稳定的。

有更好的方法吗?

【问题讨论】:

  • 你为什么在这里使用 Uri?从您的代码看来,您正在尝试处理文件路径。这并不是 Uri 真正关注的焦点——它支持这一点的唯一原因是 file: URIs。它给你的值是正确的——如果你在调试器中查看 uri1 和 uri2,你会看到它们是 file:///C:/my%20folder/ 和 file:///C:/ 所以正确相对 URI 确实是我的 %20 文件夹。大概您正在寻找相对文件系统路径?这在 Uris 的世界中并不是一个真正的概念,所以它会很笨拙。
  • @IanGriffiths:我在网上找到了一个使用 URI 进行路径差异的实现,到目前为止,这似乎是一个相当干净的实现。我仍然认为它比其他解决方案更好,就代码行数而言。

标签: c# vb.net winforms uri


【解决方案1】:

(注意:与其说是悲叹,不如说是一个答案,但我希望其中一些能提供信息。)

如果您在这里尝试获取两个文件系统路径之间的相对路径,那么您最好还是坚持使用文件系统 API。

最初Calculating the path relative to some root- the inverse of Path.Combine 的问题看起来与您的目标相同,在我对这一段应用 EDIT 之前,我建议查看它。但仔细观察后发现,在我写这篇文章时,那里的解决方案并不是那么好。

我对@9​​87654322@ 的担心是它是围绕 URI 路径的规则设计的,这些规则不一定与文件系统路径的规则相同。例如,URI 规范说“。”的路径段。是“打算在相对路径引用的开头使用”,而对于文件系统路径,将它们放在路径中间是完全合法的(如果有点奇怪)。例如。 c:\.\a\.\b\.\c 合法,与c:\a\b\c 含义相同。

众所周知,文件系统规范化很容易出错,因此可能还有比这更微妙的问题。

所以理论上,文件系统特定的 API 会比使用旨在处理 URI 的代码更好,希望它能产生适用于文件系统的结果。在实践中,.NET 似乎没有提供文件系统感知 API 来计算相对路径,令人惊讶的是,用于此目的的 Win32 API PathRelativePathTo 得到了那个“.”。发错了...

【讨论】:

    【解决方案2】:

    [编辑] 好的,经过一番思考,我确实想出了一个替代方案,但它可能并不适合所有人:

    void Main()
    {
        var path1 = @"C:\Program Files\Internet Explorer\";
        var path2 = @"C:\temp\";
        var sb = new StringBuilder(1000);
        PathRelativePathTo(sb, path1, 0, path2, 0);
        sb.ToString().Dump();
    }
    
    /*
    BOOL PathRelativePathTo(
      _Out_  LPTSTR pszPath,
      _In_   LPCTSTR pszFrom,
      _In_   DWORD dwAttrFrom,
      _In_   LPCTSTR pszTo,
      _In_   DWORD dwAttrTo
    );
    */
    [DllImport("Shlwapi.dll")]
    [return:MarshalAs(UnmanagedType.Bool)]
    public static extern bool PathRelativePathTo(
        [Out] StringBuilder result,
        [In] string pathFrom,
        [In] int dwAttrFrom,
        [In] string pathTo,
        [In] int dwAttrTo);
    

    哦,刚刚有了一个想法 - 这是否(或多或少)满足了您的需求?

    public string PathDiff(string path1, string path2)
    {
        var replace1 = path1.Replace(path2, string.Empty);
        var replace2 = path2.Replace(path1, string.Empty);
        return Path.IsPathRooted(replace1) ? replace2 : replace1;
    }
    

    或者可能更好:

    public string PathDiff(string path1, string path2)
    {
        return path1.Length > path2.Length ? 
            path1.Replace(path2, string.Empty) : 
            path2.Replace(path1, string.Empty);
    }
    

    (编辑:derp,过早点击提交):

    不幸的是,没有内置的相对路径帮助器,但你基本上已经掌握了它,就像这样:

    var path1 = @"C:\dev\src\release\Frontend\";
    var path2 = @"C:\dev\src\";
    
    var path1Uri = new Uri(path1);
    var path2Uri = new Uri(path2);
    
    var from1to2 = path1Uri.MakeRelativeUri(path2Uri).OriginalString;
    var from2to1 = path2Uri.MakeRelativeUri(path1Uri).OriginalString;
    
    Console.WriteLine("To go from {0} to {1}, you need to {2}", path1, path2, from1to2);
    Console.WriteLine("To go from {0} to {1}, you need to {2}", path2, path1, from2to1);
    

    输出:

    To go from C:\dev\src\release\Frontend\ to C:\dev\src\, you need to ../../
    To go from C:\dev\src\ to C:\dev\src\release\Frontend\, you need to release/Frontend/
    

    现在,对于“\”与“/”的斜线差异,如果将最终结果包装在 Path.GetFullPath 中,它将自动解决差异:

    Console.WriteLine(Path.GetFullPath(Path.Combine(path1, from1to2)));
    Console.WriteLine(Path.GetFullPath(Path.Combine(path2, from2to1)));
    

    输出:

    C:\dev\src\
    C:\dev\src\release\Frontend\
    

    【讨论】:

    • 转义怎么办,比如%20?另外,我需要保持相对路径级别,所以我不能做GetFullPath。用作winrar进程的参数,否则会导致目录结构错误。
    • @Neolisk 啊,如果您需要相对路径格式,我不确定Uri.UnescapeDataString 是否有替代方案...我会考虑一下,但您可能已经有了“最佳”解决方案。
    • 我认为您的 PathDiff 只能处理一条路径是另一条路径的子路径的情况。无论路径如何嵌套,URI 方法都有效。考虑这个例子:“C:\my folder\123\7876”减去“C:\test\879\123”应该等于这个“..\..\my folder\123\7876”。
    • 另一个例子是“C:\my folder\”减去“X:\”得到“C:\my folder\”,意思是MakeRelativeUri尽力解析相对路径,但在这种情况下只有绝对路径有效,它很好地解决了这一点。
    • 是的,很遗憾没有“融入” IO 命名空间,但我认为您所拥有的可能是您的方案的最佳解决方案......我想到任何事情,我'会在这里编辑,但不希望......