Как я могу сравнивать (каталог) в С#?

Если у меня есть два объекта DirectoryInfo, как я могу их сравнить для семантического равенства? Например, следующие пути должны считаться равными C:\temp:

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

Следующее может быть или не быть равно C:\temp:

  • \temp, если текущий рабочий каталог находится на диске C:\
  • temp, если текущий рабочий каталог C:\
  • C:\temp.
  • C:\temp...\

Если важно рассмотреть текущий рабочий каталог, я могу понять это сам, так что это не так важно. Трейлинг точек удаляются в окнах, поэтому эти пути действительно должны быть равными - но они не разделяются на unix, поэтому в моно я ожидаю других результатов.

Чувствительность к регистру не является обязательной. Пути могут существовать или не существовать, и пользователь может иметь или не иметь разрешений на путь - я бы предпочел бы быстрый надежный метод, который не требует ввода-вывода (поэтому проверки на отсутствие), но если что-то построено - Я тоже был бы доволен чем-то "хорошим"...

Ответ 1

Из этого ответа этот метод может обрабатывать несколько случаев:

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

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)

Ответ 3

Есть несколько коротких путей к реализации путей в .NET. Об этом много жалоб. Патрик Смаккия, создатель NDepend, опубликовал библиотеку с открытым исходным кодом

Ответ 4

Я понимаю, что это старый пост, но все ответы в конечном итоге основаны на текстовом сравнении двух имен. Попытка получить два "нормализованных" имени, которые принимают во внимание множество возможных способов ссылки на один и тот же файл-объект, почти невозможна. Существуют такие проблемы, как: соединения, символические ссылки, общие сетевые файлы (ссылки на один и тот же файл в разных манерах) и т.д. И т.д.

Вопрос специально запросил, чтобы решение не требовало ввода-вывода, но если вы собираетесь иметь дело с сетевыми путями, вам абсолютно необходимо выполнить IO: бывают случаи, когда просто невозможно определить из любого локального манипуляции с путями, будут ли ссылки на два файла ссылаться на один и тот же физический файл. (Это можно легко понять следующим образом: предположим, что файловый сервер имеет соединение с каталогом Windows где-то внутри общего поддерева. В этом случае файл может ссылаться либо непосредственно, либо через соединение. Но соединение находится на файловом сервере, и поэтому для клиента просто невозможно определить, только через локальную информацию, что два ссылочных имени файла относятся к одному и тому же физическому файлу: информация просто не доступна локально для клиента. Таким образом, необходимо абсолютно выполнить минимальный IO - например, открыть два дескриптора файловых объектов - определить, ссылаются ли ссылки на один и тот же физический файл.)

Следующее решение выполняет некоторые операции ввода-вывода, но правильно определяет, являются ли две ссылки файловой системы семантически идентичными, то есть ссылаются на один и тот же файл-объект. (если ни одна спецификация файла относится к допустимому файловому объекту, все ставки отключены):

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

Идея для этого была получена от ответа Уоррена Стивенса в аналогичном вопросе, который я опубликовал на SuperUser: https://superuser.com/a/881966/241981

Ответ 5

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

Ответ 6

Кажется, что P/Invoking GetFinalPathNameByHandle() будет самым надежным решением.

UPD: Ой, я не принимал во внимание ваше желание не использовать никаких операций ввода-вывода

Ответ 7

Свойства "Имя" равны. Возьмем:

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, чтобы гарантировать, что полный путь одинаков.

EDIT: это работает для вашей ситуации? Создайте консольное приложение и вставьте его во весь файл 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;
        }
    }
}

Ответ 8

Вы можете использовать Minimatch, порт Node.js 'minimash.

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 Синтаксис Minimatch был разработан для путей в стиле Linux (только с косой чертой). В частности, он использует обратную косую черту как escape-символ, поэтому он не может просто принимать пути в стиле Windows. Моя версия С# сохраняет это поведение.

Чтобы подавить это и разрешить как обратную косую черту, так и косые черты в качестве разделителей путей (в шаблонах или вводе), установите параметр AllowWindowsPaths:

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

Передача этой опции полностью отключит escape-символы.

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

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

Ответ 10

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

?

Ответ 11

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

Ответ 12

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

Конструктор Uri нормализует путь.