Il modo migliore per determinare se due percorsi fanno riferimento allo stesso file in C #

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

  •  03-07-2019
  •  | 
  •  

Domanda

Nel prossimo Java7, c'è un nuova API per verificare se due oggetti file sono lo stesso riferimento file.

Esistono API simili fornite in .NET framework?

L'ho cercato su MSDN ma nulla mi illumina.

Lo voglio semplice ma non voglio confrontare per nome file che causerà problemi con collegamenti hard / simbolici e diverso stile di percorso. (ad es. \\? \ C: \ , C: \ ).

Quello che ho intenzione di fare è semplicemente impedire che i file duplicati vengano trascinati e rilasciati nella mia lista di collegamenti.

È stato utile?

Soluzione

Per quanto posso vedere (1) (2) (3) (4) , il modo in cui lo fa JDK7, è chiamando GetFileInformationByHandle sui file e confrontando dwVolumeSerialNumber, nFileIndexHigh e nFileIndexLow.

Per MSDN:

  

È possibile confrontare i membri VolumeSerialNumber e FileIndex restituiti nella struttura BY_HANDLE_FILE_INFORMATION per determinare se due percorsi sono associati alla stessa destinazione; ad esempio, puoi confrontare due percorsi di file e determinare se sono associati alla stessa directory.

Non credo che questa funzione sia inclusa in .NET, quindi dovrai usare P / Invoke .

Potrebbe funzionare o meno con i file di rete. Secondo MSDN:

  

A seconda dei componenti di rete sottostanti del sistema operativo e del tipo di server collegato, la funzione GetFileInformationByHandle potrebbe non riuscire, restituire informazioni parziali o informazioni complete per il file specificato.

Un test rapido mostra che funziona come previsto (stessi valori) con un collegamento simbolico su un sistema Linux collegato tramite SMB / Samba, ma che non è in grado di rilevare che un file è lo stesso quando si accede utilizzando condivisioni diverse che puntano al stesso file (FileIndex è lo stesso, ma VolumeSerialNumber differisce).

Altri suggerimenti

Modifica : tieni presente che @Rasmus Faber menziona Funzione GetFileInformationByHandle nell'API Win32 e questo fa quello che vuoi , controlla e vota il suo rispondi per ulteriori informazioni.


Penso che tu abbia bisogno di una funzione del sistema operativo per darti le informazioni che desideri, altrimenti avrà dei falsi negativi qualunque cosa tu faccia.

Ad esempio, si riferiscono allo stesso file?

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

Vorrei esaminare quanto sia fondamentale per te non avere file duplicati nell'elenco, quindi fare solo del mio meglio.

Detto questo, esiste un metodo nella classe Path che può fare parte del lavoro: Path.GetFullPath , almeno espanderà il percorso a nomi lunghi, secondo la struttura esistente. Successivamente si confrontano solo le stringhe. Non sarà infallibile e non gestirà i due link sopra nel mio esempio.

Risposta: Non esiste un modo infallibile per confrontare i percorsi di base delle stringhe per determinare se puntano allo stesso file.

La ragione principale è che percorsi apparentemente non correlati possono puntare allo stesso identico file che fa ai reindirizzamenti del file system (giunzioni, collegamenti simbolici, ecc ...). Ad esempio

" d: \ temp \ foo.txt " & Quot; c: \ othertemp \ foo.txt "

Questi percorsi possono potenzialmente puntare allo stesso file. Questo caso elimina chiaramente qualsiasi funzione di confronto delle stringhe come base per determinare se due percorsi puntano allo stesso file.

Il livello successivo sta confrontando le informazioni sul file del sistema operativo. Aprire il file per due percorsi e confrontare le informazioni sull'handle. In Windows questo può essere fatto con GetFileInformationByHandle. Lucian Wischik ha fatto un eccellente post su questo argomento qui.

Tuttavia, esiste ancora un problema con questo approccio. Funziona solo se l'account utente che esegue il controllo è in grado di aprire entrambi i file per la lettura. Esistono numerosi elementi che possono impedire a un utente di aprire uno o entrambi i file. Compreso ma non limitato a ...

  • Mancanza di autorizzazioni sufficienti per il file
  • Mancanza di autorizzazioni sufficienti per una directory nel percorso del file
  • Modifica del file system che si verifica tra l'apertura del primo file e il secondo, ad esempio una disconnessione della rete.

Quando inizi a esaminare tutti questi problemi, inizi a capire perché Windows non fornisce un metodo per determinare se due percorsi sono uguali. Non è solo una domanda facile / possibile a cui rispondere.

Ecco un'implementazione C # di IsSameFile utilizzando 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;
    }
  }
}

Per prima cosa ho pensato che fosse davvero semplice ma questo non funziona:

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

Forse questa libreria aiuta http://www.codeplex.com/FileDirectoryPath . Non l'ho usato da solo.

modifica: vedi questo esempio su quel sito:

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

Se hai bisogno di confrontare gli stessi nomi di file più e più volte, ti suggerirei di cercare di canonizzare quei nomi.

In un sistema Unix, c'è realpath () che canonizza il tuo percorso. Penso che sia generalmente la migliore scommessa se hai un percorso complesso . Tuttavia, è probabile che non riesca sui volumi montati tramite connessioni di rete.

Tuttavia, in base all'approccio realpath (), se si desidera supportare più volumi, inclusi i volumi di rete, è possibile scrivere la propria funzione che controlla ogni nome di directory in un percorso e se fa riferimento a un volume, quindi determinare se il riferimento di volume in entrambi i percorsi è lo stesso. Detto questo, il punto di montaggio potrebbe essere diverso (ovvero il percorso sul volume di destinazione potrebbe non essere la radice di quel volume), quindi non è così facile risolvere tutti i problemi lungo il percorso, ma è definitivamente possibile (altrimenti come funzionerebbe in primo luogo ?!)

Una volta che i nomi dei file sono stati correttamente canonizzati, un semplice confronto di stringhe ti dà la risposta corretta.

La risposta dei The Rasmus è probabilmente il modo più veloce se non hai bisogno di confrontare più volte gli stessi nomi di file.

Puoi sempre eseguire una codifica MD5 su entrambi e confrontare il risultato. Non esattamente efficiente, ma più semplice del confronto manuale dei file.

Ecco un post su come MD5 una stringa in C # .

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top