Как я могу сравнивать (каталог) в С#?
Если у меня есть два объекта 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
Ответ 9
Microsoft реализовала подобные методы, хотя они не так полезны, как ответы выше:
Ответ 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 нормализует путь.