39

I'm hoping there is a built in .NET method to do this, but I'm not finding it.

I have two paths that I know to be on the same root drive, I want to be able to get a relative path from one to the other.

string path1 = @"c:\dir1\dir2\";
string path2 = @"c:\dir1\dir3\file1.txt";
string relPath = MysteryFunctionThatShouldExist(path1, path2); 
// relPath == "..\dir3\file1.txt"

Does this function exist? If not what would be the best way to implement it?

Bert Lamb
  • 2,317
  • 2
  • 20
  • 26

2 Answers2

67

Uri works:

Uri path1 = new Uri(@"c:\dir1\dir2\");
Uri path2 = new Uri(@"c:\dir1\dir3\file1.txt");
Uri diff = path1.MakeRelativeUri(path2);
string relPath = diff.OriginalString;
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • 4
    Uri does work but will switch to forward slashes, which is easy enough to fix. Thanks! – Bert Lamb Nov 19 '09 at 21:57
  • 3
    There is not only forward slashes to take care ! Better add UnescapeDataString. string relPath = Uri.UnescapeDataString(diff.OriginalString); – Edeen Jan 31 '19 at 15:49
  • As of .Net 5 / .Net Core 2 there's also `MakeRelative`; which seems to do the same as `MakeRelativeUri(...).OriginalString`. https://learn.microsoft.com/en-us/dotnet/api/system.uri.makerelative?view=net-5.0 – JohnLBevan Dec 09 '20 at 19:42
  • This does not work for paths with spaces, as those are converted to %20. And the escaped format does not work everywhere in windows. In my case .lnk files. – user168345 Dec 22 '22 at 11:05
  • Make sure `path1` has a trailing `Path.DirtectorySeparatorChar` or else your relative path will include the inner-most directory name. – djrecipe Feb 27 '23 at 10:40
16

You could also import the PathRelativePathTo function and call it.

e.g.:

using System.Runtime.InteropServices;

public static class Util
{
  [DllImport( "shlwapi.dll", EntryPoint = "PathRelativePathTo" )]
  protected static extern bool PathRelativePathTo( StringBuilder lpszDst,
      string from, UInt32 attrFrom,
      string to, UInt32 attrTo );

  public static string GetRelativePath( string from, string to )
  {
    StringBuilder builder = new StringBuilder( 1024 );
    bool result = PathRelativePathTo( builder, from, 0, to, 0 );
    return builder.ToString();
  }
}
Leviculus
  • 161
  • 1
  • 2
  • Works for me, but I had to remove the "protected", otherwise (with VS2012, .NET3.5) I get the error CS1057: "PathRelativePathTo(System.Text.StringBuilder, string, uint, string, uint)': static classes cannot contain protected members" – V-R Jan 26 '16 at 17:59
  • Importing win32 API for a simple case like that seems to be overdoing it, though it's nice to know it's possible. – FacelessPanda Aug 08 '17 at 15:33
  • @FacelessPanda It's hardly overdoing it - the library is almost certainly loaded anyway so using it has zero overhead. – Ian Goldby Jan 19 '18 at 11:42
  • 1
    @FacelessPanda you would be very disappointed to see the .NET Framework source code then! :) System.Windows.Forms in particular: https://referencesource.microsoft.com/#System.Windows.Forms,namespaces – Adam Plocher Jul 08 '18 at 00:37
  • @AdamPlocher Your given example is not really a surprise though since Windows Forms was never something else than a WinAPI wrapper. – Ray Mar 14 '21 at 16:26