自最初提出问题以来以及最初发布此答案以来,已对该问题进行了编辑和澄清。就目前的问题而言,下面的这个答案不是正确的答案。本质上,当前的问题是要求进行纯文本路径比较,这与想要确定两条路径是否解析为同一个文件系统对象完全不同。所有其他答案,除了 Igor Korkhov 的答案,最终都是基于两个名字的文本比较。
如果你真的想知道两个路径何时解析到同一个文件系统对象,你必须做一些 IO。试图获得两个“标准化”名称,考虑到引用同一文件对象的无数可能方式,几乎是不可能的。存在诸如:连接点、符号链接、网络文件共享(以不同方式引用同一文件对象)等问题。事实上,除了 Igor Korkhov 的之外,上面的每个答案都绝对会给出在某些情况下不正确导致问题“这两个路径是否引用相同的文件系统对象。(例如连接、符号链接、目录链接等)
该问题特别要求该解决方案不需要任何 I/O,但如果您要处理网络路径,则绝对需要进行 IO:在某些情况下根本无法从任何本地确定路径字符串操作,两个文件引用是否将引用同一个物理文件。 (这很容易理解如下。假设文件服务器在共享子树的某处有一个 windows 目录联结。在这种情况下,可以直接引用文件,也可以通过联结引用文件。但联结位于 并且由文件服务器解析,因此客户端根本不可能仅通过本地信息来确定两个引用文件名引用同一个物理文件:该信息在本地根本不可用到客户端。因此必须绝对做一些最小的 IO - 例如打开两个文件对象句柄 - 以确定引用是否引用相同的物理文件。)
以下解决方案会进行一些 IO,虽然非常少,但可以正确确定两个文件系统引用在语义上是否相同,即引用同一个文件对象。 (如果两个文件规范都没有引用有效的文件对象,则所有赌注都关闭):
public static bool AreDirsEqual(string dirName1, string dirName2, bool resolveJunctionaAndNetworkPaths = true)
{
if (string.IsNullOrEmpty(dirName1) || string.IsNullOrEmpty(dirName2))
return dirName1==dirName2;
dirName1 = NormalizePath(dirName1); //assume NormalizePath normalizes/fixes case and path separators to Path.DirectorySeparatorChar
dirName2 = NormalizePath(dirName2);
int i1 = dirName1.Length;
int i2 = dirName2.Length;
do
{
--i1; --i2;
if (i1 < 0 || i2 < 0)
return i1 < 0 && i2 < 0;
} while (dirName1[i1] == dirName2[i2]);//If you want to deal with international character sets, i.e. if NormalixePath does not fix case, this comparison must be tweaked
if( !resolveJunctionaAndNetworkPaths )
return false;
for(++i1, ++i2; i1 < dirName1.Length; ++i1, ++i2)
{
if (dirName1[i1] == Path.DirectorySeparatorChar)
{
dirName1 = dirName1.Substring(0, i1);
dirName2 = dirName1.Substring(0, i2);
break;
}
}
return AreFileSystemObjectsEqual(dirName1, dirName2);
}
public static bool AreFileSystemObjectsEqual(string dirName1, string dirName2)
{
//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);
return objectFileInfo1 != null
&& objectFileInfo2 != null
&& (objectFileInfo1.Value.FileIndexHigh == objectFileInfo2.Value.FileIndexHigh)
&& (objectFileInfo1.Value.FileIndexLow == objectFileInfo2.Value.FileIndexLow)
&& (objectFileInfo1.Value.VolumeSerialNumber == objectFileInfo2.Value.VolumeSerialNumber);
}
}
static SafeFileHandle GetFileHandle(string dirName)
{
const int FILE_ACCESS_NEITHER = 0;
//const int FILE_SHARE_READ = 1;
//const int FILE_SHARE_WRITE = 2;
//const int FILE_SHARE_DELETE = 4;
const int FILE_SHARE_ANY = 7;//FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE
const int CREATION_DISPOSITION_OPEN_EXISTING = 3;
const int FILE_FLAG_BACKUP_SEMANTICS = 0x02000000;
return CreateFile(dirName, FILE_ACCESS_NEITHER, FILE_SHARE_ANY, System.IntPtr.Zero, CREATION_DISPOSITION_OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, System.IntPtr.Zero);
}
static BY_HANDLE_FILE_INFORMATION? GetFileInfo(SafeFileHandle directoryHandle)
{
BY_HANDLE_FILE_INFORMATION objectFileInfo;
if ((directoryHandle == null) || (!GetFileInformationByHandle(directoryHandle.DangerousGetHandle(), out objectFileInfo)))
{
return null;
}
return objectFileInfo;
}
[DllImport("kernel32.dll", EntryPoint = "CreateFileW", CharSet = CharSet.Unicode, SetLastError = true)]
static extern SafeFileHandle CreateFile(string lpFileName, int dwDesiredAccess, int dwShareMode,
IntPtr SecurityAttributes, int dwCreationDisposition, int dwFlagsAndAttributes, IntPtr hTemplateFile);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool GetFileInformationByHandle(IntPtr hFile, out BY_HANDLE_FILE_INFORMATION lpFileInformation);
[StructLayout(LayoutKind.Sequential)]
public struct BY_HANDLE_FILE_INFORMATION
{
public uint FileAttributes;
public System.Runtime.InteropServices.ComTypes.FILETIME CreationTime;
public System.Runtime.InteropServices.ComTypes.FILETIME LastAccessTime;
public System.Runtime.InteropServices.ComTypes.FILETIME LastWriteTime;
public uint VolumeSerialNumber;
public uint FileSizeHigh;
public uint FileSizeLow;
public uint NumberOfLinks;
public uint FileIndexHigh;
public uint FileIndexLow;
};
请注意,在上面的代码中,我包含了两行类似dirName1 = NormalizePath(dirName1); 的代码,并且没有指定函数NormalizePath 是什么。 NormalizePath 可以是任何路径规范化函数 - 许多已在此问题其他地方的答案中提供。提供合理的NormalizePath 函数意味着AreDirsEqual 将给出合理的答案,即使两个输入路径引用不存在的文件系统对象,即您只想在字符串级别上比较的路径。 (上面 Ishmaeel 的评论也应该注意,这段代码没有这样做......)
(此代码可能存在细微的权限问题,如果用户对某些初始目录具有仅遍历权限,我不确定是否允许AreFileSystemObjectsEqual 要求的文件系统访问。在这种情况下,参数resolveJunctionaAndNetworkPaths 至少允许用户恢复到纯文本比较...)
这个想法来自 Warren Stevens 在我在 SuperUser 上发布的一个类似问题的回复:https://superuser.com/a/881966/241981