Лучший способ определить, есть ли две пути ссылки на один и тот же файл в C # - PullRequest
36 голосов
/ 04 января 2009

В следующей версии Java7 есть новый API , чтобы проверить, являются ли два файловых объекта одинаковыми ссылочными файлами.

Существуют ли аналогичные API в .NET Framework?

Я искал его по MSDN, но ничто не просветило меня.

Я хочу, чтобы все было просто, но я не хочу сравнивать по имени файла, что вызовет проблемы с жесткими / символическими ссылками и другим стилем пути. (например, \\?\C:\, C:\).

Что я собираюсь сделать, это просто предотвратить перетаскивание дубликата файла в мой список ссылок.

Ответы [ 7 ]

33 голосов
/ 04 января 2009

Насколько я вижу (1) (2) (3) (4) , как это делает JDK7 это путем вызова GetFileInformationByHandle для файлов и сравнения dwVolumeSerialNumber, nFileIndexHigh и nFileIndexLow.

За MSDN:

Можно сравнить элементы VolumeSerialNumber и FileIndex, возвращенные в структуре BY_HANDLE_FILE_INFORMATION, чтобы определить, отображаются ли два пути на одну цель; например, вы можете сравнить два пути к файлам и определить, соответствуют ли они одному и тому же каталогу.

Я не думаю, что эта функция упакована в .NET, поэтому вам придется использовать P / Invoke .

Может работать или не работать для сетевых файлов. По данным MSDN:

В зависимости от базовых сетевых компонентов операционной системы и типа сервера, к которому подключен, функция GetFileInformationByHandle может завершиться ошибкой, вернуть частичную информацию или полную информацию для данного файла.

Быстрый тест показывает, что он работает должным образом (те же значения) с символической ссылкой в ​​системе Linux, подключенной с использованием SMB / Samba, но что он не может обнаружить, что файл одинаков при обращении с использованием других общих ресурсов, которые указывают на тот же файл (FileIndex такой же, но VolumeSerialNumber отличается).

7 голосов
/ 04 января 2009

Edit : обратите внимание, что @ Rasmus Faber упоминает функцию GetFileInformationByHandle в API Win32, и это делает то, что вы хотите, проверяете и повышаете его ответ для получения дополнительной информации.


Я думаю, что вам нужна функция ОС, чтобы дать вам нужную информацию, в противном случае она будет иметь некоторые ложные негативы, что бы вы ни делали.

Например, относятся ли они к одному и тому же файлу?

  • \ сервер \ общий ресурс \ путь \ filename.txt
  • \ сервер \ d $ \ Temp \ путь \ filename.txt

Я бы проанализировал, насколько важно, чтобы в вашем списке не было повторяющихся файлов, а затем приложил бы все усилия.

Сказав это, в классе Path есть метод, который может выполнять часть работы: Path.GetFullPath , он по крайней мере расширит путь до длинных имен в соответствии с существующей структурой. После этого вы просто сравниваете строки. Это не будет надежно, и не будет обрабатывать две ссылки выше в моем примере.

2 голосов
/ 04 августа 2015

Вот реализация на C # IsSameFile с использованием GetFileInformationByHandle:

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;
    }
  }
}
2 голосов
/ 04 января 2009

Ответ. Не существует надежного способа сравнения базовых путей строк, чтобы определить, указывают ли они на один и тот же файл.

Основная причина заключается в том, что, казалось бы, несвязанные пути могут указывать на точно такой же файл, что и перенаправления файловой системы (соединения, символические ссылки и т. Д.). Например

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

Эти пути могут указывать на один и тот же файл. Этот случай явно исключает любую функцию сравнения строк в качестве основы для определения, указывают ли два пути на один и тот же файл.

Следующий уровень - сравнение информации о файле ОС. Откройте файл для двух путей и сравните информацию дескриптора. В окнах это можно сделать с помощью GetFileInformationByHandle. Lucian Wischik сделал отличную запись на эту тему здесь.

Однако в этом подходе все еще есть проблема. Это работает только в том случае, если учетная запись пользователя, выполняющего проверку, может открыть оба файла для чтения. Существует множество элементов, которые могут помешать пользователю открыть один или оба файла. Включая, но не ограничиваясь ...

  • Отсутствие достаточных разрешений для файла
  • Отсутствие достаточных прав доступа к каталогу в пути к файлу
  • Изменение файловой системы, которое происходит между открытием первого файла и второго, например отключение от сети.

Когда вы начинаете рассматривать все эти проблемы, вы начинаете понимать, почему Windows не предоставляет метод для определения того, совпадают ли два пути. Это просто не простой / возможный вопрос.

1 голос
/ 04 января 2009

Сначала я подумал, что это действительно легко, но это не работает:

  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. Я сам ею не пользовался.

edit: Смотрите этот пример на этом сайте:

  //
  // 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);
0 голосов
/ 18 сентября 2012

Если вам нужно сравнивать одни и те же имена файлов снова и снова, я бы посоветовал вам заняться канонализацией этих имен.

В системе Unix есть функция realpath () , которая канонизирует ваш путь. Я думаю, что это, как правило, лучшая ставка, если у вас есть сложный путь. Тем не менее, это может произойти сбой на томах, подключенных через сетевые подключения.

Однако, основываясь на подходе realpath (), если вы хотите поддерживать несколько томов, включая сетевые тома, вы можете написать свою собственную функцию, которая проверяет каждое имя каталога в пути и, если он ссылается на том, то определяет, является ли ссылка на том томом. в обоих направлениях одинаково. При этом точка монтирования может отличаться (т. Е. Путь к целевому тому не может быть корнем этого тома), поэтому не так просто решить все проблемы на этом пути, но это определенно возможно (иначе как это сработало бы в первую очередь?!)

После правильного канонализации имен файлов простое сравнение строк дает правильный ответ.

Ответ Расмуса, вероятно, самый быстрый способ, если вам не нужно сравнивать одни и те же имена файлов снова и снова.

0 голосов
/ 04 января 2009

Вы всегда можете выполнить кодирование MD5 в обоих случаях и сравнить результат. Не совсем эффективно, но проще, чем сравнивать файлы вручную.

Вот пост на , как MD5 строка в C # .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...