如何比较C#中的(目录)路径?

时间:2010-02-17 14:42:33

标签: c# .net path filesystems

如果我有两个DirectoryInfo个对象,我怎样才能将它们与语义相等进行比较?例如,以下路径都应被视为等于C:\temp

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

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

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

如果考虑当前的工作目录很重要,我可以自己解决这个问题,所以这并不重要。尾随点在窗口中被剥离,因此这些路径确实应该相等 - 但它们不会在unix中被剥离,因此在单声道下我会期望其他结果。

区分大小写是可选的。路径可能存在也可能不存在,用户可能拥有或可能没有路径权限 - 我更喜欢快速健壮的方法,不需要任何I / O(所以没有权限检查),但是如果有什么内置的话 - 我对任何“足够好”的东西感到满意......

13 个答案:

答案 0 :(得分:91)

除了大小写差异(GetFullPath)和尾随斜杠之外,

Path.GetFullPath("test") != Path.GetFullPath("TEST")似乎可以完成这项工作。 因此,以下代码应该可以正常工作:

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

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

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

答案 1 :(得分:31)

this answer开始,此方法可以处理一些边缘情况:

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

应该适用于文件和目录路径。

答案 2 :(得分:11)

.NET中的路径实现有一些简短。有很多关于它的抱怨。 NDepend的创建者Patrick Smacchia发布了open source library that enables handling of common and complex path operations。如果您对应用程序中的路径执行大量比较操作,则此库可能对您有用。

答案 3 :(得分:7)

我意识到这是一篇旧帖子,但所有答案最终都是基于两个名字的文字比较。试图获得两个“规范化”名称,几乎不可能考虑到引用相同文件对象的无数可能方式。存在诸如:交汇点,符号链接,网络文件共享(以不同方式引用相同文件对象)等问题。事实上,除了Igor Korkhov之外,上面的每个答案都绝对会给出在某些情况下结果不正确(例如,路口,符号链接,目录链接等)

该问题特别要求解决方案不需要任何I / O,但如果您要处理网络路径,则绝对需要执行IO:有些情况下无法从任何本地确定path-string操作,两个文件引用是否会引用相同的物理文件。 (这可以很容易理解如下。假设文件服务器在共享子树中的某个地方有一个Windows目录连接。在这种情况下,可以直接或通过连接引用文件。但是联结位于文件服务器上,因此,客户端根本无法通过本地信息确定两个引用文件名是指同一个物理文件:客户端本地无法获得信息。因此必须绝对做一些最小的IO - 例如,打开两个文件对象句柄 - 确定引用是否引用相同的物理文件。)

以下解决方案执行一些IO,尽管非常小,但正确地确定两个文件系统引用是否在语义上相同,即引用相同的文件对象。 (如果文件规范都没有引用有效的文件对象,则所有投注均已关闭):

    public static bool AreFileSystemObjectsEqual(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;
    }

这个想法来自Warren Stevens在我在SuperUser上发布的类似问题的回复:https://superuser.com/a/881966/241981

答案 4 :(得分:3)

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

答案 5 :(得分:3)

P / Invoking GetFinalPathNameByHandle()似乎是最可靠的解决方案。

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

答案 6 :(得分:2)

Microsoft已经实现了类似的方法,尽管它们没有上述答案那么有用:

答案 7 :(得分:1)

“名称”属性相同。取:

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”.dir3 ==“4760”。)只有FullName属性不同。

您可以使用递归方法检查给定两个DirectoryInfo类的每个父级的Name属性,以确保完整路径相同。

编辑:这适用于您的情况吗?创建一个控制台应用程序并将其粘贴到整个Program.cs文件中。向AreEquals()函数提供两个DirectoryInfo对象,如果它们是同一目录,它将返回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;
        }
    }
}

答案 8 :(得分:1)

您可以使用Minimatch,它是Node.js迷你匹配的端口。

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


See why AllowWindowsPaths = true选项是必要的:

  

在Windows风格的路径上   Minimatch的语法是为Linux风格的路径设计的(仅限正斜杠)。特别是,它使用反斜杠作为转义字符,因此它不能简单地接受Windows样式的路径。我的C#版本保留了这种行为。

     

要禁止此操作,并允许反斜杠和正斜杠作为路径分隔符(在模式或输入中),请设置AllowWindowsPaths选项:

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

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

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

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

答案 9 :(得分:0)

bool equals = myDirectoryInfo1.FullName == myDirectoryInfo2.FullName;

答案 10 :(得分:0)

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

答案 11 :(得分:0)

bool Equals(string path1, string path2)
{
    return new Uri(path1) == new Uri(path2);
}

Uri构造函数规范化路径。

答案 12 :(得分:0)

我使用递归为自己解决了这个问题。

 public bool PathEquals(string Path1, string Path2)
 {
     FileInfo f1 = new FileInfo(Path1.Trim('\\','/','.'));
     FileInfo f2 = new FileInfo(Path2.Trim('\\', '/','.'));
     if(f1.Name.ToLower() == f2.Name.ToLower())
     {
         return DirectoryEquals(f1.Directory, f2.Directory);
     }
     else
     {
         return false;
     }
}

public bool DirectoryEquals(DirectoryInfo d1, DirectoryInfo d2)
{
    if(d1.Name.ToLower() == d2.Name.ToLower())
    {
        if((d1.Parent != null) && (d2.Parent != null))
        {
            return DirectoryEquals(d1.Parent, d2.Parent);
        }
        else
        {
            return true;//C:\Temp1\Temp2 equals \Temp1\Temp2
            //return (d1.Parent == null) && (d2.Parent == null);//C:\Temp1\Temp2 does not equal \Temp1\Temp2
        }
    }
    else
    {
        return false;
    }
}

注意:new FileInfo(path)返回有效的FileInfo,即使path不是文件(名称字段等于目录名称)