我如何比较(目录)在C#中的path?

如果我有两个DirectoryInfo对象,我怎么能比较它们的语义相等? 例如,下面的path应该全部被认为等于C:\temp

  • C:\temp
  • C:\temp\
  • C:\temp\.
  • C:\temp\x\..\..\temp\.

以下可能会或可能不会等于C:\temp

  • \temp如果当前工作目录位于驱动器C:\
  • 如果当前工作目录是C:\ temp
  • C:\temp.
  • C:\temp...\

如果考虑当前的工作目录很重要,我可以自己弄清楚,所以这并不重要。 尾随点在窗口被剥离,所以这些path应该是相等的 – 但它们并没有在unix中被剥离,所以在单声道下,我期望得到其他结果。

区分大小写是可选的。 path可能存在也可能不存在,并且用户可能有或没有对path的权限 – 我更喜欢快速健壮的方法,不需要任何I / O(因此没有权限检查),但是如果有我也很高兴有什么“足够好”的

从这个答案 ,这个方法可以处理一些边缘情况:

 public static string NormalizePath(string path) { return Path.GetFullPath(new Uri(path).LocalPath) .TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) .ToUpperInvariant(); } 

更多的细节在原来的答案。 像这样称呼它:

 bool pathsEqual = NormalizePath(path1) == NormalizePath(path2); 

应该适用于文件和目录path。

除了大小写差异( Path.GetFullPath("test") != Path.GetFullPath("TEST") )和尾部斜线之外, GetFullPath似乎完成了这项工作。 所以,下面的代码应该可以正常工作:

 String.Compare( Path.GetFullPath(path1).TrimEnd('\\'), Path.GetFullPath(path2).TrimEnd('\\'), StringComparison.InvariantCultureIgnoreCase) 

或者,如果您想从DirectoryInfo开始:

 String.Compare( dirinfo1.FullName.TrimEnd('\\'), dirinfo2.FullName.TrimEnd('\\'), StringComparison.InvariantCultureIgnoreCase) 

在.NET中有一些简单的实现path。 有很多抱怨。 NDepend的创build者Patrick Smacchia发布了一个开源库,可以处理常见和复杂的path操作 。 如果您在应用程序的path上进行了大量比较操作,则该库可能对您有用。

我意识到这是一个旧的post,但所有的答案最终都是基于两个名字的文本比较。 试图获得两个“规范化”的名字,这是考虑到无数可能的方法来引用同一个文件对象,几乎是不可能的。 存在的问题有:路口,符号链接,networking文件共享(以不同的方式引用同一个文件对象)等等。

这个问题特别要求解决scheme不需要任何I / O,但是如果你打算处理联网path,你将绝对需要做IO:在某些情况下,根本不可能从任何本地pathstring操纵,是否两个文件引用将引用相同的物理文件。 (这可以很容易理解如下:假设一个文件服务器在共享子树的某个地方有一个windows目录结点,在这种情况下,一个文件可以被直接引用或者通过结点引用,但是结点驻留在文件服务器上,所以客户根本不可能仅仅通过本地信息来确定这两个参考文件的名称是指同一个物理文件:这些信息在客户端本地是不可用的,因此必须做一些最小的IO – 例如打开两个文件对象句柄 – 以确定引用是否引用同一个物理文件。)

以下解决scheme执行一些IO操作,但是正确地确定两个文件系统引用是否在语义上相同,即引用相同的文件对象。 (如果两个文件规范都没有引用有效的文件对象,则所有的投注都closures):

  public static bool AreDirsEqual(string dirName1, string dirName2) { //Optimization: if strings are equal, don't bother with the IO bool bRet = string.Equals(dirName1, dirName2, StringComparison.OrdinalIgnoreCase); if (!bRet) { //NOTE: we cannot lift the call to GetFileHandle out of this routine, because we _must_ // have both file handles open simultaneously in order for the objectFileInfo comparison // to be guaranteed as valid. using (SafeFileHandle directoryHandle1 = GetFileHandle(dirName1), directoryHandle2 = GetFileHandle(dirName2)) { BY_HANDLE_FILE_INFORMATION? objectFileInfo1 = GetFileInfo(directoryHandle1); BY_HANDLE_FILE_INFORMATION? objectFileInfo2 = GetFileInfo(directoryHandle2); bRet = objectFileInfo1 != null && objectFileInfo2 != null && (objectFileInfo1.Value.FileIndexHigh == objectFileInfo2.Value.FileIndexHigh) && (objectFileInfo1.Value.FileIndexLow == objectFileInfo2.Value.FileIndexLow) && (objectFileInfo1.Value.VolumeSerialNumber == objectFileInfo2.Value.VolumeSerialNumber); } } return bRet; } 

这个想法来自于沃伦史蒂文斯在一个类似的问题,我发布在超级用户的回复: https : //superuser.com/a/881966/241981

  System.IO.Path.GetFullPath(pathA).Equals(System.IO.Path.GetFullPath(PathB)); 

看来P /调用GetFinalPathNameByHandle()将是最可靠的解决scheme。

UPD:哎呀,我没有考虑到你不想使用任何I / O的愿望

“名称”属性是相等的。 采取:

 DirectoryInfo dir1 = new DirectoryInfo("C:\\Scratch"); DirectoryInfo dir2 = new DirectoryInfo("C:\\Scratch\\"); DirectoryInfo dir3 = new DirectoryInfo("C:\\Scratch\\4760"); DirectoryInfo dir4 = new DirectoryInfo("C:\\Scratch\\4760\\..\\"); 

dir1.Name == dir2.Name and dir2.Name == dir4.Name (在这种情况下是“Scratch” dir1.Name == dir2.Name and dir2.Name == dir4.Name ==“4760”。)它只是不同的FullName属性。

您可能能够执行recursion方法来检查给定两个DirectoryInfo类的每个父级的Name属性,以确保完整的path是相同的。

编辑 :这工作适合你的情况? 创build一个控制台应用程序并粘贴这整个Program.cs文件。 将两个DirectoryInfo对象提供给AreEquals()函数,如果它们是相同的目录,它将返回True。 如果你愿意的话,你也许可以把这个AreEquals()方法调整成DirectoryInfo的扩展方法,所以你可以做myDirectoryInfo.IsEquals(myOtherDirectoryInfo);

 using System; using System.Diagnostics; using System.IO; using System.Collections.Generic; namespace ConsoleApplication3 { class Program { static void Main(string[] args) { Console.WriteLine(AreEqual( new DirectoryInfo("C:\\Scratch"), new DirectoryInfo("C:\\Scratch\\"))); Console.WriteLine(AreEqual( new DirectoryInfo("C:\\Windows\\Microsoft.NET\\Framework"), new DirectoryInfo("C:\\Windows\\Microsoft.NET\\Framework\\v3.5\\1033\\..\\.."))); Console.WriteLine(AreEqual( new DirectoryInfo("C:\\Scratch\\"), new DirectoryInfo("C:\\Scratch\\4760\\..\\.."))); Console.WriteLine("Press ENTER to continue"); Console.ReadLine(); } private static bool AreEqual(DirectoryInfo dir1, DirectoryInfo dir2) { DirectoryInfo parent1 = dir1; DirectoryInfo parent2 = dir2; /* Build a list of parents */ List<string> folder1Parents = new List<string>(); List<string> folder2Parents = new List<string>(); while (parent1 != null) { folder1Parents.Add(parent1.Name); parent1 = parent1.Parent; } while (parent2 != null) { folder2Parents.Add(parent2.Name); parent2 = parent2.Parent; } /* Now compare the lists */ if (folder1Parents.Count != folder2Parents.Count) { // Cannot be the same - different number of parents return false; } bool equal = true; for (int i = 0; i < folder1Parents.Count && i < folder2Parents.Count; i++) { equal &= folder1Parents[i] == folder2Parents[i]; } return equal; } } } 

你可以使用Minimatch,Node.js的minimatch的一个端口。

 var mm = new Minimatcher(searchPattern, new Options { AllowWindowsPaths = true }); if (mm.IsMatch(somePath)) { // The path matches! Do some cool stuff! } var matchingPaths = mm.Filter(allPaths); 

看看为什么 AllowWindowsPaths = true选项是必要的:

在Windows风格的path上Minimatch的语法是为Linux风格的pathdevise的(只有正斜杠)。 特别是它使用反斜杠作为转义字符,所以它不能简单地接受Windows风格的path。 我的C#版本保留了这种行为。

要抑制这种情况,并允许反斜杠和正斜杠作为path分隔符(在模式或input中),请设置AllowWindowsPaths选项:

 var mm = new Minimatcher(searchPattern, new Options { AllowWindowsPaths = true }); 

传递此选项将完全禁用转义字符。

Nuget: http ://www.nuget.org/packages/Minimatch/

GitHub: https //github.com/SLaks/Minimatch

微软已经实现了类似的方法,虽然它们不如上面的答案:

  • PathUtil.ArePathsEqual方法 (它只是return string.Equals(path1, path2, StringComparison.OrdinalIgnoreCase);
  • PathUtil.Normalize方法
  • PathUtil.NormalizePath方法 (这只是return PathUtil.Normalize(path);
 bool equals = myDirectoryInfo1.FullName == myDirectoryInfo2.FullName; 

 using System; using System.Collections.Generic; using System.Text; namespace EventAnalysis.IComparerImplementation { public sealed class FSChangeElemComparerByPath : IComparer<FSChangeElem> { public int Compare(FSChangeElem firstPath, FSChangeElem secondPath) { return firstPath.strObjectPath == null ? (secondPath.strObjectPath == null ? 0 : -1) : (secondPath.strObjectPath == null ? 1 : ComparerWrap(firstPath.strObjectPath, secondPath.strObjectPath)); } private int ComparerWrap(string stringA, string stringB) { int length = 0; int start = 0; List<string> valueA = new List<string>(); List<string> valueB = new List<string>(); ListInit(ref valueA, stringA); ListInit(ref valueB, stringB); if (valueA.Count != valueB.Count) { length = (valueA.Count > valueB.Count) ? valueA.Count : valueB.Count; if (valueA.Count != length) { for (int i = 0; i < length - valueA.Count; i++) { valueA.Add(string.Empty); } } else { for (int i = 0; i < length - valueB.Count; i++) { valueB.Add(string.Empty); } } } else length = valueA.Count; return RecursiveComparing(valueA, valueB, length, start); } private void ListInit(ref List<string> stringCollection, string stringToList) { foreach (string s in stringToList.Remove(0, 2).Split('\\')) { stringCollection.Add(s); } } private int RecursiveComparing(List<string> valueA, List<string> valueB, int length, int start) { int result = 0; if (start != length) { if (valueA[start] == valueB[start]) { result = RecursiveComparing(valueA, valueB, length, ++start); } else { result = String.Compare(valueA[start], valueB[start]); } } else return 0; return result; } } } 
 bool Equals(string path1, string path2) { return new Uri(path1) == new Uri(path2); } 

Uri构造函数规范化path。