There are lot’s of ways propagated on the internet on how to check if two given paths actually refer to the same, i.e. are identical.
That is obviously nested in the many ways one could represent the same path.
For example all of the following could actually be the same file system object:
But even if you think about normalizing paths first, that is creating absolute full qualified, redundancy free string representations and then comparing them, depending on the file system type, case or not case sensitive, you could still miss out.
Think about junctions, or (NTFS) reparse points.
Lately, I needed to do such a comparison and came up with the following function:
public static bool IsSamePath(string path1, string path2)
{
if (path1 == null)
throw new ArgumentNullException("path1");
if (path2 == null)
throw new ArgumentNullException("path2");
using (var stream1 = NativeMethods.OpenRead(path1))
using (var stream2 = NativeMethods.OpenRead(path2))
{
NativeMethods.BY_HANDLE_FILE_INFORMATION info1;
if (!NativeMethods.GetFileInformationByHandle(stream1, out info1))
{
throw new IOException(string.Format(
"Retrieving the file information for '{0}' failed.", path1),
new Win32Exception());
}
NativeMethods.BY_HANDLE_FILE_INFORMATION info2;
if (!NativeMethods.GetFileInformationByHandle(stream2, out info2))
{
throw new IOException(string.Format(
"Retrieving the file information for '{0}' failed.", path2),
new Win32Exception());
}
return (info1.dwVolumeSerialNumber == info2.dwVolumeSerialNumber &&
info1.nFileIndexHigh == info2.nFileIndexHigh &&
info1.nFileIndexLow == info2.nFileIndexLow);
}
}
This function has the following traits:
The respective NativeMethods
class looks like this:
internal static class NativeMethods
{
[StructLayout(LayoutKind.Sequential)]
internal struct FILETIME
{
internal uint dwLowDateTime;
internal uint dwHighDateTime;
}
[StructLayout(LayoutKind.Sequential)]
internal struct BY_HANDLE_FILE_INFORMATION
{
internal uint dwFileAttributes;
internal FILETIME ftCreationTime;
internal FILETIME ftLastAccessTime;
internal FILETIME ftLastWriteTime;
internal uint dwVolumeSerialNumber;
internal uint nFileSizeHigh;
internal uint nFileSizeLow;
internal uint nNumberOfLinks;
internal uint nFileIndexHigh;
internal uint nFileIndexLow;
}
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto, BestFitMapping = false)]
internal static extern SafeFileHandle CreateFile(
string lpFileName,
int dwDesiredAccess,
FileShare dwShareMode,
IntPtr lpSecurityAttributes,
FileMode dwCreationDisposition,
int dwFlagsAndAttributes,
IntPtr hTemplateFile);
internal static SafeFileHandle OpenRead(string name)
{
return CreateFile(name,
0, // "FileAccess.Neither" Read nor Write
FileShare.Read,
IntPtr.Zero,
FileMode.Open,
// Required to "open" directories (see MSDN: https://msdn.microsoft.com/en-us/library/windows/desktop/aa363858%28v=vs.85%29.aspx)
(int)FileAttributes.BackupSemantics,
IntPtr.Zero);
}
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool GetFileInformationByHandle(
SafeFileHandle hFile,
out BY_HANDLE_FILE_INFORMATION lpFileInformation);
}