La mejor manera de determinar si dos referencias de ruta al mismo archivo en C #

StackOverflow https://stackoverflow.com/questions/410705

  •  03-07-2019
  •  | 
  •  

Pregunta

En el próximo Java7, hay un nueva API para verificar si dos objetos de archivo son la misma referencia de archivo.

¿Se proporciona una API similar en el marco .NET?

Lo he buscado en MSDN pero nada me ilumina.

Lo quiero simple pero no quiero comparar por nombre de archivo, lo que causará problemas con los enlaces simbólicos / duros y con diferentes estilos de ruta. (por ejemplo, \\? \ C: \ , C: \ ).

Lo que voy a hacer es evitar que el archivo duplicado se arrastre y se suelte en mi lista de enlaces.

¿Fue útil?

Solución

Por lo que puedo ver (1) (2) (3) (4) , la forma en que JDK7 lo hace, es llamando a GetFileInformationByHandle en los archivos y comparando dwVolumeSerialNumber, nFileIndexHigh y nFileIndexLow.

Por MSDN:

  

Puede comparar los miembros VolumeSerialNumber y FileIndex devueltos en la estructura BY_HANDLE_FILE_INFORMATION para determinar si dos rutas se asignan al mismo destino; por ejemplo, puede comparar dos rutas de archivos y determinar si se asignan al mismo directorio.

No creo que esta función esté envuelta por .NET, por lo que tendrá que usar P / Invocar .

Puede o no funcionar para archivos de red. Según MSDN:

  

Dependiendo de los componentes de red subyacentes del sistema operativo y del tipo de servidor conectado, la función GetFileInformationByHandle puede fallar, devolver información parcial o completa para el archivo dado.

Una prueba rápida muestra que funciona como se esperaba (mismos valores) con un enlace simbólico en un sistema Linux conectado usando SMB / Samba, pero que no puede detectar que un archivo es el mismo cuando se accede usando diferentes recursos compartidos que apuntan a la mismo archivo (FileIndex es el mismo, pero VolumeSerialNumber difiere).

Otros consejos

Editar : tenga en cuenta que @Rasmus Faber menciona GetFileInformationByHandle en la api de Win32, y esto hace lo que usted desea , verifique y aumente su contesta para más información.


Creo que necesitas una función de SO para darte la información que deseas, de lo contrario, tendrá algunos falsos negativos, hagas lo que hagas.

Por ejemplo, ¿se refieren al mismo archivo?

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

Examinaría qué tan crítico es para usted no tener archivos duplicados en su lista y luego hacer un mejor esfuerzo.

Habiendo dicho eso, hay un método en la clase Path que puede hacer parte del trabajo: Path.GetFullPath , al menos expandirá la ruta a nombres largos, de acuerdo con la estructura existente. Después solo comparas las cuerdas. Sin embargo, no será infalible y no manejará los dos enlaces anteriores en mi ejemplo.

Respuesta: No existe una manera infalible de comparar las rutas de base de cadena para determinar si apuntan al mismo archivo.

La razón principal es que, aparentemente, las rutas no relacionadas pueden apuntar exactamente al mismo archivo que las redirecciones del sistema de archivos (uniones, enlaces simbólicos, etc.). Por ejemplo

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

Estas rutas pueden apuntar al mismo archivo. Este caso elimina claramente cualquier función de comparación de cadenas como base para determinar si dos rutas apuntan al mismo archivo.

El siguiente nivel es comparar la información del archivo del sistema operativo. Abra el archivo para dos rutas y compare la información del controlador. En Windows, esto se puede hacer con GetFileInformationByHandle. Lucian Wischik hizo un excelente publicar sobre este tema aquí.

Sin embargo, todavía hay un problema con este enfoque. Solo funciona si la cuenta de usuario que realiza la verificación puede abrir ambos archivos para su lectura. Hay numerosos elementos que pueden evitar que un usuario abra uno o ambos archivos. Incluyendo pero no limitado a ...

  • Falta de permisos suficientes para presentar
  • Falta de permisos suficientes para un directorio en la ruta del archivo
  • Cambio del sistema de archivos que se produce entre la apertura del primer archivo y el segundo, como una desconexión de la red.

Cuando empiezas a ver todos estos problemas, empiezas a entender por qué Windows no proporciona un método para determinar si dos rutas son iguales. Simplemente no es una pregunta fácil / posible para responder.

Aquí hay una implementación en C # de IsSameFile usando 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;
    }
  }
}

Primero pensé que es muy fácil, pero no funciona :

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

Tal vez esta biblioteca ayude a http://www.codeplex.com/FileDirectoryPath . No lo he usado yo mismo.

editar: vea este ejemplo en ese sitio:

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

Si necesitas comparar los mismos nombres de archivo una y otra vez, te sugiero que estudies la canonalización de esos nombres.

Bajo un sistema Unix, hay La función realpath () que canonaliza tu ruta. En general, creo que es la mejor opción si tiene una ruta compleja . Sin embargo, es probable que falle en volúmenes montados a través de conexiones de red.

Sin embargo, en función del enfoque realpath (), si desea admitir varios volúmenes, incluidos los volúmenes de red, puede escribir su propia función que verifica cada nombre de directorio en una ruta y si hace referencia a un volumen, luego determine si la referencia del volumen En ambos caminos es el mismo. Dicho esto, el punto de montaje puede ser diferente (es decir, la ruta en el volumen de destino puede no ser la raíz de ese volumen), por lo que no es tan fácil resolver todos los problemas en el camino, pero es definitivamente posible (de lo contrario, cómo ¿Funcionaría en primer lugar ?!)

Una vez que los nombres de los archivos correctamente canonizados, una simple comparación de cadenas te da la respuesta correcta.

La respuesta de Rasmus es probablemente la forma más rápida si no necesitas comparar los mismos nombres de archivos una y otra vez.

Siempre se puede realizar una codificación MD5 en ambos y comparar el resultado. No es exactamente eficiente, pero es más fácil que comparar los archivos manualmente.

Aquí hay una publicación en cómo MD5 una cadena en C # .

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top