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

Den*_*s C 37 c# filesystems api

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

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

我通过MSDN搜索它,但没有任何启发我.

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

我要做的是防止重复文件被拖放到我的链接列表中.

Ras*_*ber 33

据我所知(1) (2) (3) (4),JDK7的工作方式是通过调用文件的GetFileInformationByHandle并比较dwVolumeSerialNumber,nFileIndexHigh和nFileIndexLow.

每个MSDN:

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

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

它可能适用于网络文件,也可能不适用.根据MSDN:

根据操作系统的基础网络组件和连接到的服务器类型,GetFileInformationByHandle函数可能会失败,返回部分信息或给定文件的完整信息.

快速测试显示它在使用SMB/Samba连接的Linux系统上使用符号链接按预期工作(相同值),但是当使用指向同一文件的不同共享访问时,它无法检测到文件是否相同( FileIndex是相同的,但VolumeSerialNumber不同).

  • 此功能是否适用于网络文件? (2认同)

ang*_*son 8

编辑:请注意@Rasmus Faber在Win32 api中提到了GetFileInformationByHandle函数,这可以满足您的需求,查看并提供他的答案以获取更多信息.


我认为你需要一个操作系统功能来提供你想要的信息,否则无论你做什么都会产生一些漏报.

例如,这些是指同一个文件吗?

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

我会检查你的列表中没有重复文件是多么重要,然后尽一切努力.

话虽如此,Path类中有一个方法可以完成一些工作:Path.GetFullPath,它至少会根据现有结构扩展到长名称的路径.之后你只需比较字符串.虽然它不会万无一失,但在我的例子中不会处理上面的两个链接.


Max*_*nce 5

下面是一个C#实现IsSameFile使用GetFileInformationByHandle

本地方法.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);
}
Run Code Online (Sandbox Code Playgroud)

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;
    }
  }
}
Run Code Online (Sandbox Code Playgroud)