Лучший способ определить, есть ли ссылка на два пути к одному файлу в С#

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

Есть ли аналогичный API, предоставляемый в платформе .NET?

Я просматриваю его по MSDN, но ничего не просвещаю.

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

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

Ответ 1

Насколько я могу видеть (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 отличается).

Ответ 2

Изменить. Обратите внимание: @Rasmus Faber упоминает GetFileInformationByHandle в Win32 api, и это делает то, что вы хотите, проверьте и подтвердите его для получения дополнительной информации.


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

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

  • \сервер\папка\путь\filename.txt
  • \сервер\d $\ Temp\путь\filename.txt

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

Сказав это, в классе Path есть способ, который может выполнить некоторую работу: Path.GetFullPath, он будет в наименее расширить путь к длинным именам, в соответствии с существующей структурой. Впоследствии вы просто сравниваете строки. Однако он не будет надежным и не будет обрабатывать две ссылки выше в моем примере.

Ответ 3

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

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

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

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

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

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

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

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

Ответ 4

Вот реализация С# 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;
    }
  }
}

Ответ 5

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

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

Ответ 6

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

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

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

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

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

Ответ 7

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

Вот сообщение на как MD5 строка на С#.