views:

963

answers:

7

If I have two DirectoryInfo objects, how can I compare them for semantic equality? For example, the following paths should all be considered equal to C:\temp:

  • C:\temp
  • C:\temp\
  • C:\temp\.
  • C:\temp\x\..\..\temp\.

The following may or may not be equal to C:\temp:

  • \temp if the current working directory is on drive C:\
  • temp if the current working directory is C:\
  • C:\temp.
  • C:\temp...\

If it's important to consider the current working directory, I can figure that out myself, so that's not that important. Trailing dots are stripped in windows, so those paths really should be equal - but they aren't stripped in unix, so under mono I'd expect other results.

Case sensitivity is optional. The paths may or may not exist, and the user may or may not have permissions to the path - I'd prefer a fast robust method that doesn't require any I/O (so no permission checking), but if there's something built-in I'd be happy with anything "good enough" too...

+1  A: 
bool equals = myDirectoryInfo1.FullName == myDirectoryInfo2.FullName;

?

herzmeister der welten
This is the easy version of Binary Worrier's solution. Please note there is a problem however with the trailing slash: "c:\temp" is unequal to "c:\temp\".
Steven
Ok, so if I normalize out the trailing slashes and potentially the casing - and accept the fact that it does some FileIOPermission stuff - this looks like a good start, thanks!
Eamon Nerbonne
+1  A: 
 System.IO.Path.GetFullPath(pathA).Equals(System.IO.Path.GetFullPath(PathB));
Asad Butt
A: 

It seems that P/Invoking GetFinalPathNameByHandle() would be the most reliable solution.

UPD: Oops, I didn't take into account your desire not to use any I/O

Igor Korkhov
Well, I'd *prefer* no I/O, but a simple I/O using solution is better than writing something from scratch...
Eamon Nerbonne
@Eamon Nerbonne: my solution has two more downsides: 1) it will work only on Vista and newer OSs 2) it won't work if at least one of the paths does not exist. But it also has one benefit: it works with symbolic links, i.e. answers your question "how can I compare them for semantic equality?"; while `GetFullPath()` doesn't. So it's up to you to decide if you need *real* semantic euqality or not.
Igor Korkhov
+1  A: 

The "Name" properties are equal. Take:

DirectoryInfo dir1 = new DirectoryInfo("C:\\Scratch");
DirectoryInfo dir2 = new DirectoryInfo("C:\\Scratch\\");
DirectoryInfo dir3 = new DirectoryInfo("C:\\Scratch\\4760");
DirectoryInfo dir4 = new DirectoryInfo("C:\\Scratch\\4760\\..\\");

dir1.Name == dir2.Name and dir2.Name == dir4.Name ("Scratch" in this case. dir3 == "4760".) It's only the FullName properties that are different.

You might be able to do a recursive method to examine the Name properties of each parent given your two DirectoryInfo classes to ensure the complete path is the same.

EDIT: does this work for your situation? Create a Console Application and paste this over the entire Program.cs file. Provide two DirectoryInfo objects to the AreEquals() function and it will return True if they're the same directory. You might be able to tweak this AreEquals() method to be an extension method on DirectoryInfo if you like, so you could just do myDirectoryInfo.IsEquals(myOtherDirectoryInfo);

using System;
using System.Diagnostics;
using System.IO;
using System.Collections.Generic;

namespace ConsoleApplication3
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(AreEqual(
                new DirectoryInfo("C:\\Scratch"),
                new DirectoryInfo("C:\\Scratch\\")));

            Console.WriteLine(AreEqual(
                new DirectoryInfo("C:\\Windows\\Microsoft.NET\\Framework"),
                new DirectoryInfo("C:\\Windows\\Microsoft.NET\\Framework\\v3.5\\1033\\..\\..")));

            Console.WriteLine(AreEqual(
                new DirectoryInfo("C:\\Scratch\\"),
                new DirectoryInfo("C:\\Scratch\\4760\\..\\..")));

            Console.WriteLine("Press ENTER to continue");
            Console.ReadLine();
        }

        private static bool AreEqual(DirectoryInfo dir1, DirectoryInfo dir2)
        {
            DirectoryInfo parent1 = dir1;
            DirectoryInfo parent2 = dir2;

            /* Build a list of parents */
            List<string> folder1Parents = new List<string>();
            List<string> folder2Parents = new List<string>();

            while (parent1 != null)
            {
                folder1Parents.Add(parent1.Name);
                parent1 = parent1.Parent;
            }

            while (parent2 != null)
            {
                folder2Parents.Add(parent2.Name);
                parent2 = parent2.Parent;
            }

            /* Now compare the lists */

            if (folder1Parents.Count != folder2Parents.Count)
            {
                // Cannot be the same - different number of parents
                return false;
            }

            bool equal = true;

            for (int i = 0; i < folder1Parents.Count && i < folder2Parents.Count; i++)
            {
                equal &= folder1Parents[i] == folder2Parents[i];
            }

            return equal;
        }
    }
}
Andy Shellam
The Name property will only return the name of the deepest subdirectory, so "c:\foo\bar" will return "bar". Comparing "d:\foo\bar" with "c:\bar" will result true, which is not good.
Steven
That's why you do a *recursive* comparison on all parents! Please remove the downvote, this is a perfectly acceptable solution. I've modified my answer with a full code sample.
Andy Shellam
Hmm, this works even for `D:\temp` vs. `C:\temp`. Fine idea; you didn't deal with case sensitivity though, and it could be a bit shorter: while(dir1!=null else {dir1=dir1.Parent; dir2=dir2.Parent;} return dir1==dir2;
Eamon Nerbonne
Yeah case sensitivity is a difficult one because the OP wanted code to work on both Mono and Windows, but on Linux 2 names of different case are considered different, but on Windows they're considered to be the same file, so it's a per-platform decision.
Andy Shellam
+2  A: 

There are some short comes to the implementation of paths in .NET. There are many complaints about it. Patrick Smacchia, the creator of NDepend, published an open source library that enables handling of common and complex path operations. If you do a lot of compare operations on paths in your application, this library might be useful to you.

Steven
Hmm, interesting - have you used it?
Eamon Nerbonne
Nope, I havent.
Steven
+7  A: 

GetFullPath seems to do the work, except for case difference (Path.GetFullPath("test")!=Path.GetFullPath("TEST")) and trailing slash. So, the following code should work fine:

String.Compare(
    Path.GetFullPath(path1).TrimEnd('\\'),
    Path.GetFullPath(path2).TrimEnd('\\'), 
    StringComparison.InvariantCultureIgnoreCase)

Or, if you want to start with DirectoryInfo:

String.Compare(
    dirinfo1.FullName.TrimEnd('\\'),
    dirinfo2.FullName.TrimEnd('\\'), 
    StringComparison.InvariantCultureIgnoreCase)
VladV
+1 for a complete, concise answer.
Programming Hero
You could do Path.GetFullPath(pathx).ToUpperInvariant().TrimEnd('\\') to get rid of the case sensitivity. This should be used with caution on UNIX though, as UNIX treats two names of different case as different folders, whereas Windows treats them as one and the same.
Andy Shellam
If you edit this to be case invariant and use DirectoryInfo's (via FullName), you'll have a perfect answer :-)
Eamon Nerbonne
@Eamon, I've added DirectoryInfo variant for you :-) . And it was case invariant already - that's what StringComparison.InvariantCultureIgnoreCase does.
VladV
Ah yes, I'm blind :-) - marked as answer
Eamon Nerbonne
Oh, and in case some other user stumbles across this answer; FullName *does* require path-discovery security permissions and is sensitive to the current-working-directory (which effectively means you can only compare absolute paths - or relative paths as evaluated in the CWD).
Eamon Nerbonne