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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/410705/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 02:18:44  来源:igfitidea点击:

Best way to determine if two path reference to same file in C#

c#apifilesystems

提问by Dennis C

In the upcoming Java7, there is a new APIto check if two file object are same file reference.

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

Are there similar API provided in the .NET framework?

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

I've search it over MSDN but nothing enlighten me.

我已经在 MSDN 上搜索过它,但没有任何启发。

I want it simple but I don't want to compare by filename which will cause problems with hard/symbolic links and different style of path. (e.g. \\?\C:\, C:\).

我希望它简单,但我不想按文件名进行比较,这会导致硬/符号链接和路径样式不同的问题。(例如\\?\C:\C:\)。

What I going to do is just prevent duplicated file being drag and dropped to my linklist.

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

采纳答案by Rasmus Faber

As far as I can see (1)(2)(3)(4), the way JDK7 does it, is by calling GetFileInformationByHandleon the files and comparing dwVolumeSerialNumber, nFileIndexHigh and nFileIndexLow.

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

Per MSDN:

每 MSDN:

You can compare the VolumeSerialNumber and FileIndex members returned in the BY_HANDLE_FILE_INFORMATION structure to determine if two paths map to the same target; for example, you can compare two file paths and determine if they map to the same directory.

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

I do not think this function is wrapped by .NET, so you will have to use P/Invoke.

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

It might or might not work for network files. According to MSDN:

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

Depending on the underlying network components of the operating system and the type of server connected to, the GetFileInformationByHandle function may fail, return partial information, or full information for the given file.

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

A quick test shows that it works as expected (same values) with a symbolic link on a Linux system connected using SMB/Samba, but that it cannot detect that a file is the same when accessed using different shares that point to the same file (FileIndex is the same, but VolumeSerialNumber differs).

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

回答by Lasse V. Karlsen

Edit: Note that @Rasmus Fabermentions the GetFileInformationByHandlefunction in the Win32 api, and this does what you want, check and upvote his answerfor more information.

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



I think you need an OS function to give you the information you want, otherwise it's going to have some false negatives whatever you do.

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

For instance, does these refer to the same file?

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

  • \server\share\path\filename.txt
  • \server\d$\temp\path\filename.txt
  • \server\share\path\filename.txt
  • \server\d$\temp\path\filename.txt

I would examine how critical it is for you to not have duplicate files in your list, and then just do some best effort.

我会检查列表中没有重复文件对您来说有多重要,然后尽最大努力。

Having said that, there is a method in the Path class that can do some of the work: Path.GetFullPath, it will at least expand the path to long names, according to the existing structure. Afterwards you just compare the strings. It won't be foolproof though, and won't handle the two links above in my example.

话虽如此,Path 类中有一个方法可以做一些工作:Path.GetFullPath,它至少会根据现有结构将路径扩展为长名称。之后,您只需比较字符串。但它不会万无一失,并且不会处理我的示例中的上述两个链接。

回答by Soviut

You could always perform an MD5 encode on both and compare the result. Not exactly efficient, but easier than manually comparing the files yourself.

您始终可以对两者执行 MD5 编码并比较结果。效率不高,但比自己手动比较文件更容易。

Here is a post on how to MD5 a string in C#.

这是一篇关于如何在 C# 中对字符串进行 MD5的帖子。

回答by tuinstoel

First I thought it is really easy but this doesn'twork:

首先,我认为这是很容易的,但这工作:

  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"); 
  }

Maybe this library helps http://www.codeplex.com/FileDirectoryPath. I haven't used it myself.

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

edit:See this example on that site:

编辑:在该站点上查看此示例:

  //
  // 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);

回答by JaredPar

Answer: There is no foolproof way in which you can compare to string base paths to determine if they point to the same file.

答:没有万无一失的方法可以与字符串基本路径进行比较以确定它们是否指向同一文件。

The main reason is that seemingly unrelated paths can point to the exact same file do to file system redirections (junctions, symbolic links, etc ...) . For example

主要原因是看似不相关的路径可以指向与文件系统重定向(连接、符号链接等)完全相同的文件。例如

"d:\temp\foo.txt" "c:\othertemp\foo.txt"

"d:\temp\foo.txt" "c:\othertemp\foo.txt"

These paths can potentially point to the same file. This case clearly eliminates any string comparison function as a basis for determining if two paths point to the same file.

这些路径可能指向同一个文件。这种情况显然消除了任何字符串比较函数作为确定两个路径是否指向同一个文件的基础。

The next level is comparing the OS file information. Open the file for two paths and compare the handle information. In windows this can be done with GetFileInformationByHandle. Lucian Wischik did an excellent poston this subject here.

下一级别是比较操作系统文件信息。打开两个路径的文件,比较句柄信息。在 Windows 中,这可以通过 GetFileInformationByHandle 来完成。Lucian Wischik在这里发表了一篇关于这个主题的精彩帖子

There is still a problem with this approach though. It only works if the user account performing the check is able to open both files for reading. There are numerous items which can prevent a user from opening one or both files. Including but not limited to ...

但是,这种方法仍然存在问题。它仅在执行检查的用户帐户能够打开两个文件进行读取时才有效。有许多项目可以阻止用户打开一个或两个文件。包括但不仅限于 ...

  • Lack of sufficient permissions to file
  • Lack of sufficient permissions to a directory in the path of the file
  • File system change which occurs between the opening of the first file and the second such as a network disconnection.
  • 文件权限不足
  • 对文件路径中的目录缺乏足够的权限
  • 在打开第一个文件和打开第二个文件之间发生的文件系统更改,例如网络断开连接。

When you start looking at all of these problems you begin to understand why Windows does not provide a method to determine if two paths are the same. It's just not an easy/possible question to answer.

当您开始查看所有这些问题时,您就会开始理解为什么 Windows 没有提供一种方法来确定两个路径是否相同。这不是一个容易/可能回答的问题。

回答by Alexis Wilke

If you need to compare the same filenames over and over again, I would suggest you look into canonalizing those names.

如果您需要一遍又一遍地比较相同的文件名,我建议您考虑规范化这些名称。

Under a Unix system, there is the realpath()function which canonalizes your path. I think that's generally the best bet if you have a complexpath. However, it is likely to fail on volumes mounted via network connections.

在 Unix 系统下,有realpath()函数可以规范您的路径。我认为如果你有一个复杂的路径,这通常是最好的选择。但是,它可能会在通过网络连接安装的卷上失败。

However, based on the realpath() approach, if you want to support multiple volume including network volumes, you could write your own function that checks each directory name in a path and if it references a volume then determine whether the volume reference in both paths is the same. This being said, the mount point may be different (i.e. the path on the destination volume may not be the root of that volume) so it is not that easy to solve all the problems along the way, but it is definitively possible (otherwise how would it work in the first place?!)

但是,基于 realpath() 方法,如果您想支持包括网络卷在内的多个卷,您可以编写自己的函数来检查路径中的每个目录名称,如果它引用一个卷,则确定两个路径中的卷引用是否是一样的。话虽如此,挂载点可能不同(即目标卷上的路径可能不是该卷的根),因此解决一路上的所有问题并不容易,但绝对有可能(否则如何它首先会起作用吗?!)

Once the filenames properly canonalized a simple string comparison gives you the correct answer.

一旦文件名正确规范化,一个简单的字符串比较就会为您提供正确的答案。

Rasmus answer is probably the fastest way if you don't need to compare the same filenames over and over again.

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

回答by Maxence

Here is a C# implementation of IsSameFileusing GetFileInformationByHandle:

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

NativeMethods.cs

本地方法.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

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;
    }
  }
}