确定两个path引用C#中的同一文件的最佳方法

在即将到来的Java7中,有一个新的API来检查两个文件对象是否是相同的文件引用。

.NET框架中是否提供了类似的API?

我已经通过MSDNsearch,但没有任何启发我。

我希望它很简单,但我不想通过文件名进行比较,这会导致硬/符号链接和不同风格的path问题。 (例如\\?\C:\C:\ )。

我要做的只是防止重复文件拖放到我的链接列表。

就我所见(1) (2) (3) (4)而言 ,JDK7的方式是通过调用文件的GetFileInformationByHandle并比较dwVolumeSerialNumber,nFileIndexHigh和nFileIndexLow。

每个MSDN:

您可以比较BY_HANDLE_FILE_INFORMATION结构中返回的VolumeSerialNumber和FileIndex成员,以确定两个path是否映射到相同的目标; 例如,您可以比较两个文件path,并确定它们是否映射到相同的目录。

我不认为这个函数是由.NET包装的,所以你将不得不使用P / Invoke 。

它可能会或可能不适用于networking文件。 根据MSDN:

根据操作系统的底层networking组件和连接到的服务器的types,GetFileInformationByHandle函数可能会失败,返回部分信息或给定文件的完整信息。

一个快速的testing表明,它可以像使用SMB / Samba连接的Linux系统上的符号链接一样工作(相同的值),但是当使用指向相同文件的不同共享访问时,它不能检测到文件是相同的FileIndex是相同的,但VolumeSerialNumber不同)。

编辑 :请注意, @Rasmus Faber在Win32 api中提到了GetFileInformationByHandle函数,这就是你想要的,检查和提高他的答案以获取更多信息。


我想你需要一个操作系统的function给你你想要的信息,否则无论你做什么都会有一些错误的消极的。

例如,这些是否指向相同的文件?

  • \服务器\共享\path\ FILENAME.TXT
  • \服务器\ d $ \ TEMP \path\ FILENAME.TXT

我会研究一下在你的列表中没有重复的文件是多么的重要,然后尽一切努力。

话虽如此,Path类中有一个方法可以完成一些工作: Path.GetFullPath ,根据现有的结构,它至less会扩展到长名称的path。 之后,你只是比较string。 虽然这样做不是万无一失,在我的例子中不会处理上面的两个环节。

答:没有万无一失的方法可以比较string的基本path,以确定它们是否指向同一个文件。

主要原因是表面上不相关的path可能指向完全相同的文件做文件系统redirect(联结,符号链接等)。 例如

“d:\ temp \ foo.txt”“c:\ othertemp \ foo.txt”

这些path可能指向相同的文件。 这种情况明显地消除了任何string比较函数,作为确定两个path是否指向相同文件的基础。

下一个级别是比较OS文件信息。 打开两个path的文件并比较句柄信息。 在Windows中,这可以通过GetFileInformationByHandle完成。 Lucian Wischik在这方面做了一个很好的post 。

这种方法仍然有问题。 它只适用于执行检查的用户帐户能够打开这两个文件进行阅读。 有许多项目可以防止用户打开一个或两个文件。 包括但不仅限于 …

  • 缺乏足够的文件权限
  • 缺less对文件path中目录的足够权限
  • 文件系统在第一个文件打开和第二个文件打开之间发生变化,如networking断开。

当您开始查看所有这些问题时,您将开始理解Windows为什么不提供确定两条path是否相同的方法。 这不是一个简单/可能的问题来回答。

这是一个使用GetFileInformationByHandleIsSameFile的C#实现:

NativeMethods.cs

 public static class NativeMethods { [StructLayout(LayoutKind.Explicit)] public struct BY_HANDLE_FILE_INFORMATION { [FieldOffset(0)] public uint FileAttributes; [FieldOffset(4)] public FILETIME CreationTime; [FieldOffset(12)] public FILETIME LastAccessTime; [FieldOffset(20)] public FILETIME LastWriteTime; [FieldOffset(28)] public uint VolumeSerialNumber; [FieldOffset(32)] public uint FileSizeHigh; [FieldOffset(36)] public uint FileSizeLow; [FieldOffset(40)] public uint NumberOfLinks; [FieldOffset(44)] public uint FileIndexHigh; [FieldOffset(48)] public uint FileIndexLow; } [DllImport("kernel32.dll", SetLastError = true)] public static extern bool GetFileInformationByHandle(SafeFileHandle hFile, out BY_HANDLE_FILE_INFORMATION lpFileInformation); [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern SafeFileHandle CreateFile([MarshalAs(UnmanagedType.LPTStr)] string filename, [MarshalAs(UnmanagedType.U4)] FileAccess access, [MarshalAs(UnmanagedType.U4)] FileShare share, IntPtr securityAttributes, [MarshalAs(UnmanagedType.U4)] FileMode creationDisposition, [MarshalAs(UnmanagedType.U4)] FileAttributes flagsAndAttributes, IntPtr templateFile); } 

PathUtility.cs

 public static bool IsSameFile(string path1, string path2) { using (SafeFileHandle sfh1 = NativeMethods.CreateFile(path1, FileAccess.Read, FileShare.ReadWrite, IntPtr.Zero, FileMode.Open, 0, IntPtr.Zero)) { if (sfh1.IsInvalid) Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); using (SafeFileHandle sfh2 = NativeMethods.CreateFile(path2, FileAccess.Read, FileShare.ReadWrite, IntPtr.Zero, FileMode.Open, 0, IntPtr.Zero)) { if (sfh2.IsInvalid) Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); NativeMethods.BY_HANDLE_FILE_INFORMATION fileInfo1; bool result1 = NativeMethods.GetFileInformationByHandle(sfh1, out fileInfo1); if (!result1) throw new IOException(string.Format("GetFileInformationByHandle has failed on {0}", path1)); NativeMethods.BY_HANDLE_FILE_INFORMATION fileInfo2; bool result2 = NativeMethods.GetFileInformationByHandle(sfh2, out fileInfo2); if (!result2) throw new IOException(string.Format("GetFileInformationByHandle has failed on {0}", path2)); return fileInfo1.VolumeSerialNumber == fileInfo2.VolumeSerialNumber && fileInfo1.FileIndexHigh == fileInfo2.FileIndexHigh && fileInfo1.FileIndexLow == fileInfo2.FileIndexLow; } } } 

首先,我认为这很容易,但这不起作用:

  string fileName1 = @"c:\vobp.log"; string fileName2 = @"c:\vobp.log".ToUpper(); FileInfo fileInfo1 = new FileInfo(fileName1); FileInfo fileInfo2 = new FileInfo(fileName2); if (!fileInfo1.Exists || !fileInfo2.Exists) { throw new Exception("one of the files does not exist"); } if (fileInfo1.FullName == fileInfo2.FullName) { MessageBox.Show("equal"); } 

也许这个库可以帮助http://www.codeplex.com/FileDirectoryPath 。 我自己没有用过

编辑:看到这个网站上的这个例子:

  // // Path comparison // filePathAbsolute1 = new FilePathAbsolute(@"C:/Dir1\\File.txt"); filePathAbsolute2 = new FilePathAbsolute(@"C:\DIR1\FILE.TXT"); Debug.Assert(filePathAbsolute1.Equals(filePathAbsolute2)); Debug.Assert(filePathAbsolute1 == filePathAbsolute2); 

如果你需要一遍又一遍地比较相同的文件名,我build议你看看这些名字的无调性。

在Unix系统下,有realpath()函数来调整你的path。 如果你有复杂的path,我认为这通常是最好的select。 但是,通过networking连接安装的卷可能会失败。

但是,基于realpath()方法,如果要支持包括networking卷在内的多个卷,则可以编写自己的函数来检查path中的每个目录名,并且如果它引用卷,则确定两个path中的卷引用是一样的。 这就是说,挂载点可能是不同的(即目的地卷上的path可能不是该卷的根),因此解决所有问题并不容易,但它是完全可能的(否则如何它会工作在第一位?!)

一旦文件名正确无效,一个简单的string比较会给你正确的答案。

Rasmus的答案可能是最快的方法,如果你不需要一遍又一遍比较相同的文件名。

您可以始终在两者上执行MD5编码并比较结果。 不完全有效,但比自己比较文件更容易。

这里是一个关于如何在C#中MD5string的文章 。