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

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

¿Hay una API similar proporcionada en .NET Framework?

Lo busqué en MSDN pero nada me ilumina.

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

Lo que voy a hacer es evitar que el archivo duplicado sea arrastrado y soltado a mi lista de enlaces.

Por lo que puedo ver (1) (2) (3) (4) , la forma en que lo hace JDK7 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 / Invoke .

Puede o no funcionar para archivos de red. De acuerdo con MSDN:

Según los componentes de red subyacentes del sistema operativo y el 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 (los mismos valores) con un enlace simbólico en un sistema Linux conectado utilizando SMB / Samba, pero que no puede detectar que un archivo sea el mismo cuando se accede utilizando diferentes recursos que apuntan al mismo archivo ( FileIndex es el mismo, pero VolumeSerialNumber es diferente).

Editar : Tenga en cuenta que @Rasmus Faber menciona la función GetFileInformationByHandle en la API de Win32, y esto hace lo que usted desea, verifique y modifique su respuesta para obtener más información.


Creo que necesita una función de sistema operativo para darle la información que desea; de lo contrario, tendrá algunos elementos negativos falsos haga lo que haga.

Por ejemplo, ¿se refieren al mismo archivo?

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

Examinaré lo importante que es para usted no tener archivos duplicados en su lista, y luego hacer un gran esfuerzo.

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

Respuesta: No existe una forma infalible en la que pueda comparar con las rutas base de cadenas para determinar si apuntan al mismo archivo.

La razón principal es que las rutas aparentemente no relacionadas pueden señalar el mismo archivo exacto a 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 una excelente publicación sobre este tema aquí.

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

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

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

Aquí hay una implementación 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 realmente fácil pero esto 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 necesita comparar los mismos nombres de archivo una y otra vez, le sugiero que busque canonizar esos nombres.

En un sistema Unix, existe la función realpath () que puede personalizar su ruta. Creo que esa es generalmente la mejor opción si tienes una ruta compleja . Sin embargo, es probable que falle en volúmenes montados a través de conexiones de red.

Sin embargo, según el enfoque realpath (), si desea admitir múltiples volúmenes, incluidos los de red, podría escribir su propia función que verifique cada nombre de directorio en una ruta y si hace referencia a un volumen, determine si la referencia de volumen en ambas rutas es el mismo. Dicho esto, el punto de assembly 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, ¿Funcionaría en primer lugar?!)

Una vez que los nombres de archivo se canonizaron correctamente, una comparación simple de cadenas le da la respuesta correcta.

La respuesta de Rasmus es probablemente la manera más rápida si no necesita comparar los mismos nombres de archivo una y otra vez.

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

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