Meilleure façon de déterminer si deux chemins d'accès font référence au même fichier en C #

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

  •  03-07-2019
  •  | 
  •  

Question

Dans le prochain Java7, il existe un nouvelle API pour vérifier si deux objets de fichier ont la même référence de fichier.

Existe-t-il une API similaire à celle fournie dans le framework .NET?

J'ai effectué une recherche sur MSDN mais rien ne m'éclaire.

Je veux que ce soit simple, mais je ne veux pas comparer par nom de fichier, ce qui posera des problèmes de liens symboliques / durs et de style de chemin différent. (par exemple, \\? \ C: \ , C: \ ).

Ce que je vais faire, c'est empêcher que les fichiers en double ne soient glissés et déposés dans ma liste de liens.

Était-ce utile?

La solution

Autant que je sache, (1) (2) (3) (4) , comme le fait JDK7, en appelant GetFileInformationByHandle sur les fichiers et en comparant dwVolumeSerialNumber, nFileIndexHigh et nFIndexLow.

Par MSDN:

  

Vous pouvez comparer les membres VolumeSerialNumber et FileIndex renvoyés dans la structure BY_HANDLE_FILE_INFORMATION pour déterminer si deux chemins sont mappés vers la même cible; Par exemple, vous pouvez comparer deux chemins de fichiers et déterminer s’ils correspondent au même répertoire.

Je ne pense pas que cette fonction soit encapsulée par .NET, vous devrez donc utiliser P / Invoke .

Cela pourrait ou non fonctionner pour les fichiers réseau. Selon MSDN:

  

En fonction des composants réseau sous-jacents du système d'exploitation et du type de serveur connecté, la fonction GetFileInformationByHandle peut échouer, renvoyer des informations partielles ou des informations complètes pour le fichier donné.

Un test rapide montre qu’il fonctionne comme prévu (valeurs identiques) avec un lien symbolique sur un système Linux connecté à l’aide de SMB / Samba, mais qu’il ne peut pas détecter le même fichier lorsqu’un utilisateur accède à différents partages pointant vers le même fichier (FileIndex est identique, mais VolumeSerialNumber diffère).

Autres conseils

Modifier : notez que @Rasmus Faber mentionne le GetFileInformationByHandle dans l’API Win32, et c’est ce que vous voulez. , vérifie et fait voter sa répondez pour plus d'informations.

Je pense que vous avez besoin d'une fonction de système d'exploitation pour vous donner les informations que vous souhaitez. Sinon, vous obtiendrez de faux négatifs, quoi que vous fassiez.

Par exemple, s'agit-il du même fichier?

  • \ server \ share \ path \ filename.txt
  • \ serveur \ d $ \ temp \ chemin \ nom_fichier.txt

J'examinerais à quel point il est essentiel pour vous de ne pas avoir de fichiers en double dans votre liste, puis de faire de mon mieux.

Ceci étant dit, il existe une méthode dans la classe Path qui peut effectuer une partie du travail: Path.GetFullPath , il étendra au moins le chemin d'accès aux noms longs, conformément à la structure existante. Ensuite, il vous suffit de comparer les chaînes. Cela ne sera toutefois pas infaillible et ne gérera pas les deux liens ci-dessus dans mon exemple.

Réponse: Il n’existe aucune méthode fiable permettant de comparer les chemins de base de chaînes pour déterminer s’ils pointent vers le même fichier.

La raison principale est que des chemins apparemment non liés peuvent pointer vers le même fichier que les redirections du système de fichiers (jonctions, liens symboliques, etc.). Par exemple

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

Ces chemins peuvent potentiellement pointer vers le même fichier. Ce cas élimine clairement toute fonction de comparaison de chaînes comme base pour déterminer si deux chemins d'accès pointent vers le même fichier.

Le niveau suivant compare les informations du fichier du système d'exploitation. Ouvrez le fichier pour deux chemins et comparez les informations de descripteur. Dans Windows, cela peut être fait avec GetFileInformationByHandle. Lucian Wischik a fait un excellent publiez sur ce sujet ici.

Il reste cependant un problème avec cette approche. Cela ne fonctionne que si le compte utilisateur effectuant la vérification est capable d'ouvrir les deux fichiers en lecture. Il existe de nombreux éléments pouvant empêcher un utilisateur d'ouvrir un ou les deux fichiers. Y compris, mais sans s'y limiter ...

  • Absence d'autorisations suffisantes pour archiver
  • Manque d'autorisations suffisantes pour un répertoire dans le chemin du fichier
  • Modification du système de fichiers qui se produit entre l'ouverture du premier fichier et le second, par exemple une déconnexion du réseau.

Lorsque vous commencez à examiner tous ces problèmes, vous comprenez pourquoi Windows ne fournit pas de méthode pour déterminer si deux chemins sont identiques. Ce n'est tout simplement pas une question facile / possible à laquelle répondre.

Voici une implémentation C # de IsSameFile utilisant 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;
    }
  }
}

J'ai d'abord pensé que c'était très facile, mais cela ne fonctionne pas :

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

Peut-être que cette bibliothèque aide http://www.codeplex.com/FileDirectoryPath . Je ne l'ai pas utilisé moi-même.

modifier: Voir cet exemple sur ce site:

  //
  // 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 vous avez besoin de comparer les mêmes noms de fichiers encore et encore, je vous suggère de vous pencher sur une canonisation de ces noms.

Sous un système Unix, il existe le realpath () qui canonalise votre chemin. Je pense que c'est généralement le meilleur choix si vous avez un chemin complexe . Toutefois, il est probable que les volumes montés via des connexions réseau échouent.

Toutefois, selon l'approche realpath (), si vous souhaitez prendre en charge plusieurs volumes, y compris des volumes de réseau, vous pouvez écrire votre propre fonction qui vérifie chaque nom de répertoire dans un chemin. Déterminez si la référence de volume est un volume. dans les deux chemins est le même. Ceci étant dit, le point de montage peut être différent (le chemin sur le volume de destination peut ne pas être la racine de ce volume). Il n'est donc pas si facile de résoudre tous les problèmes rencontrés, mais c'est définitivement possible (sinon, comment cela fonctionnerait-il en premier lieu?!)

Une fois les noms de fichiers correctement canonisés, une simple comparaison de chaînes vous donne la réponse correcte.

Rasmus answer est probablement le moyen le plus rapide si vous n'avez pas besoin de comparer les mêmes noms de fichiers encore et encore.

Vous pouvez toujours effectuer un codage MD5 sur les deux et comparer le résultat. Pas tout à fait efficace, mais plus facile que de comparer manuellement les fichiers vous-même.

Voici un article sur la comment MD5 une chaîne en C # .

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top