Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Члены многих типов в System.IO пространстве имен включают path параметр, позволяющий указать абсолютный или относительный путь к ресурсу файловой системы. Затем этот путь передается в API файловой системы Windows. В этом разделе рассматриваются форматы путей к файлам, которые можно использовать в системах Windows.
Традиционные пути DOS
Стандартный путь DOS может состоять из трех компонентов:
- Буква тома или диска, за которой следует разделитель томов (
:). - Имя каталога. Символ разделителя каталогов отделяет подкаталоги в иерархически вложенных каталогах.
- Необязательное имя файла. Символ разделителя каталогов разделяет путь к файлу и имя файла.
Если присутствуют все три компонента, путь является абсолютным. Если не указана буква тома или диска, а имя каталога начинается с символа разделителя каталогов, путь отсчитывается относительно корня текущего диска. В противном случае путь относительно текущего каталога. В следующей таблице показаны возможные пути к каталогу и файлу.
| Путь | Описание |
|---|---|
C:\Documents\Newsletters\Summer2018.pdf |
Абсолютный путь к файлу из корневого диска C:. |
\Program Files\Custom Utilities\StringFinder.exe |
Относительный путь от корня текущего диска. |
2018\January.xlsx |
Относительный путь к файлу в подкаталоге текущего каталога. |
..\Publications\TravelBrochure.pdf |
Относительный путь к файлу в каталоге, начиная с текущего каталога. |
C:\Projects\apilibrary\apilibrary.sln |
Абсолютный путь к файлу из корневого диска C:. |
C:Projects\apilibrary\apilibrary.sln |
Относительный путь от текущего каталога диска C:. |
Это важно
Обратите внимание на разницу между последними двумя путями. Оба указывают необязательный описатель тома (C: в обоих случаях), но первый начинается с корня указанного тома, а второй — нет. В результате первый — абсолютный путь из корневого каталога диска C:, а второй — относительный путь из текущего каталога диска C:. Использование второй формы при назначении первой является общим источником ошибок, включающих пути к файлам Windows.
Можно определить, является ли путь к файлу полным (то есть, если путь не зависит от текущего каталога и не изменяется при изменении текущего каталога), вызвав Path.IsPathFullyQualified метод. Обратите внимание, что такой путь может включать относительные сегменты каталога (. и ..) и все равно считаться полностью квалифицированным, если разрешенный путь всегда указывает на одно и то же местоположение.
В следующем примере показано различие между абсолютными и относительными путями. Предполагается, что каталог D:\FY2018\ существует и что вы не установили текущий каталог для D:\ в командной строке перед выполнением примера.
using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
public class Example2
{
public static void Main(string[] args)
{
Console.WriteLine($"Current directory is '{Environment.CurrentDirectory}'");
Console.WriteLine("Setting current directory to 'C:\\'");
Directory.SetCurrentDirectory(@"C:\");
string path = Path.GetFullPath(@"D:\FY2018");
Console.WriteLine($"'D:\\FY2018' resolves to {path}");
path = Path.GetFullPath(@"D:FY2018");
Console.WriteLine($"'D:FY2018' resolves to {path}");
Console.WriteLine("Setting current directory to 'D:\\Docs'");
Directory.SetCurrentDirectory(@"D:\Docs");
path = Path.GetFullPath(@"D:\FY2018");
Console.WriteLine($"'D:\\FY2018' resolves to {path}");
path = Path.GetFullPath(@"D:FY2018");
// This will be "D:\Docs\FY2018" as it happens to match the drive of the current directory
Console.WriteLine($"'D:FY2018' resolves to {path}");
Console.WriteLine("Setting current directory to 'C:\\'");
Directory.SetCurrentDirectory(@"C:\");
path = Path.GetFullPath(@"D:\FY2018");
Console.WriteLine($"'D:\\FY2018' resolves to {path}");
// This will be either "D:\FY2018" or "D:\FY2018\FY2018" in the subprocess. In the sub process,
// the command prompt set the current directory before launch of our application, which
// sets a hidden environment variable that is considered.
path = Path.GetFullPath(@"D:FY2018");
Console.WriteLine($"'D:FY2018' resolves to {path}");
if (args.Length < 1)
{
Console.WriteLine(@"Launching again, after setting current directory to D:\FY2018");
Uri currentExe = new(Assembly.GetExecutingAssembly().Location, UriKind.Absolute);
string commandLine = $"/C cd D:\\FY2018 & \"{currentExe.LocalPath}\" stop";
ProcessStartInfo psi = new("cmd", commandLine); ;
Process.Start(psi).WaitForExit();
Console.WriteLine("Sub process returned:");
path = Path.GetFullPath(@"D:\FY2018");
Console.WriteLine($"'D:\\FY2018' resolves to {path}");
path = Path.GetFullPath(@"D:FY2018");
Console.WriteLine($"'D:FY2018' resolves to {path}");
}
Console.WriteLine("Press any key to continue... ");
Console.ReadKey();
}
}
// The example displays the following output:
// Current directory is 'C:\Programs\file-paths'
// Setting current directory to 'C:\'
// 'D:\FY2018' resolves to D:\FY2018
// 'D:FY2018' resolves to d:\FY2018
// Setting current directory to 'D:\Docs'
// 'D:\FY2018' resolves to D:\FY2018
// 'D:FY2018' resolves to D:\Docs\FY2018
// Setting current directory to 'C:\'
// 'D:\FY2018' resolves to D:\FY2018
// 'D:FY2018' resolves to d:\FY2018
// Launching again, after setting current directory to D:\FY2018
// Sub process returned:
// 'D:\FY2018' resolves to D:\FY2018
// 'D:FY2018' resolves to d:\FY2018
// The subprocess displays the following output:
// Current directory is 'C:\'
// Setting current directory to 'C:\'
// 'D:\FY2018' resolves to D:\FY2018
// 'D:FY2018' resolves to D:\FY2018\FY2018
// Setting current directory to 'D:\Docs'
// 'D:\FY2018' resolves to D:\FY2018
// 'D:FY2018' resolves to D:\Docs\FY2018
// Setting current directory to 'C:\'
// 'D:\FY2018' resolves to D:\FY2018
// 'D:FY2018' resolves to D:\FY2018\FY2018
Imports System.IO
Imports System.Reflection
Public Module Example2
Public Sub Main(args() As String)
Console.WriteLine($"Current directory is '{Environment.CurrentDirectory}'")
Console.WriteLine("Setting current directory to 'C:\'")
Directory.SetCurrentDirectory("C:\")
Dim filePath As String = Path.GetFullPath("D:\FY2018")
Console.WriteLine($"'D:\\FY2018' resolves to {filePath}")
filePath = Path.GetFullPath("D:FY2018")
Console.WriteLine($"'D:FY2018' resolves to {filePath}")
Console.WriteLine("Setting current directory to 'D:\\Docs'")
Directory.SetCurrentDirectory("D:\Docs")
filePath = Path.GetFullPath("D:\FY2018")
Console.WriteLine($"'D:\\FY2018' resolves to {filePath}")
filePath = Path.GetFullPath("D:FY2018")
' This will be "D:\Docs\FY2018" as it happens to match the drive of the current directory
Console.WriteLine($"'D:FY2018' resolves to {filePath}")
Console.WriteLine("Setting current directory to 'C:\\'")
Directory.SetCurrentDirectory("C:\")
filePath = Path.GetFullPath("D:\FY2018")
Console.WriteLine($"'D:\\FY2018' resolves to {filePath}")
' This will be either "D:\FY2018" or "D:\FY2018\FY2018" in the subprocess. In the sub process,
' the command prompt set the current directory before launch of our application, which
' sets a hidden environment variable that is considered.
filePath = Path.GetFullPath("D:FY2018")
Console.WriteLine($"'D:FY2018' resolves to {filePath}")
If args.Length < 1 Then
Console.WriteLine("Launching again, after setting current directory to D:\FY2018")
Dim currentExe As New Uri(Assembly.GetExecutingAssembly().GetName().CodeBase, UriKind.Absolute)
Dim commandLine As String = $"/C cd D:\FY2018 & ""{currentExe.LocalPath}"" stop"
Dim psi As New ProcessStartInfo("cmd", commandLine)
Process.Start(psi).WaitForExit()
Console.WriteLine("Sub process returned:")
filePath = Path.GetFullPath("D:\FY2018")
Console.WriteLine($"'D:\\FY2018' resolves to {filePath}")
filePath = Path.GetFullPath("D:FY2018")
Console.WriteLine($"'D:FY2018' resolves to {filePath}")
End If
Console.WriteLine("Press any key to continue... ")
Console.ReadKey()
End Sub
End Module
' The example displays the following output:
' Current directory is 'C:\Programs\file-paths'
' Setting current directory to 'C:\'
' 'D:\FY2018' resolves to D:\FY2018
' 'D:FY2018' resolves to d:\FY2018
' Setting current directory to 'D:\Docs'
' 'D:\FY2018' resolves to D:\FY2018
' 'D:FY2018' resolves to D:\Docs\FY2018
' Setting current directory to 'C:\'
' 'D:\FY2018' resolves to D:\FY2018
' 'D:FY2018' resolves to d:\FY2018
' Launching again, after setting current directory to D:\FY2018
' Sub process returned:
' 'D:\FY2018' resolves to D:\FY2018
' 'D:FY2018' resolves to d:\FY2018
' The subprocess displays the following output:
' Current directory is 'C:\'
' Setting current directory to 'C:\'
' 'D:\FY2018' resolves to D:\FY2018
' 'D:FY2018' resolves to D:\FY2018\FY2018
' Setting current directory to 'D:\Docs'
' 'D:\FY2018' resolves to D:\FY2018
' 'D:FY2018' resolves to D:\Docs\FY2018
' Setting current directory to 'C:\'
' 'D:\FY2018' resolves to D:\FY2018
' 'D:FY2018' resolves to D:\FY2018\FY2018
UNC-пути
Пути универсального именования (UNC), используемые для доступа к сетевым ресурсам, имеют следующий формат:
- Имя сервера или узла, предшествующее
\\. Имя сервера может быть именем компьютера NetBIOS или IP-адресом или полным доменным именем (IPv4, а также версия 6 поддерживаются). - Имя общего ресурса, которое разделяется от имени узла с помощью
\. Вместе сервер и имя общего ресурса составляют том. - Имя каталога. Символ разделителя каталогов отделяет подкаталоги в иерархически вложенных каталогах.
- Необязательное имя файла. Символ разделителя каталогов разделяет путь к файлу и имя файла.
Ниже приведены некоторые примеры путей UNC:
| Путь | Описание |
|---|---|
\\system07\C$\ |
Корневой каталог диска C: на system07. |
\\Server2\Share\Test\Foo.txt |
Файл Foo.txt в папке Test на диске \\Server2\Share. |
UNC-пути всегда должны быть полностью квалифицированными. Они могут включать относительные сегменты каталогов (. и ..), но они должны быть частью полного пути. Относительные пути можно использовать только, если присвоить UNC-пути букву диска.
Пути устройств DOS
Операционная система Windows имеет единую объектную модель, которая указывает на все ресурсы, включая файлы. Эти пути к объектам доступны из окна консоли и предоставляются на уровне Win32 через специальную папку символических ссылок, с которыми сопоставлены устаревшие пути DOS и UNC. Доступ к этой специальной папке осуществляется с помощью синтаксиса пути к устройству DOS, который является одним из следующих вариантов:
\\.\C:\Test\Foo.txt
\\?\C:\Test\Foo.txt
Помимо идентификации диска по букве диска, можно определить том с помощью GUID тома. Это принимает форму:
\\.\Volume{b75e2c83-0000-0000-0000-602f00000000}\Test\Foo.txt
\\?\Volume{b75e2c83-0000-0000-0000-602f00000000}\Test\Foo.txt
Замечание
Синтаксис пути к устройству DOS поддерживается в реализациях .NET, работающих в Windows, начиная с .NET Core 1.1 и .NET Framework 4.6.2.
Путь устройства DOS состоит из следующих компонентов:
Описатель пути устройства (
\\.\или\\?\), который определяет путь как путь к устройству DOS.Замечание
\\?\поддерживается во всех версиях .NET Core, .NET 5+ и в .NET Framework, начиная с версии 4.6.2.Символьная ссылка на 'реальный' объект устройства (C: в случае имени диска или Volume{b75e2c83-0000-0000-0000-602f00000000} в случае GUID тома).
Первый сегмент пути устройства DOS после описателя пути устройства идентифицирует том или диск. (Например,
\\?\C:\и\\.\BootPartition\.)Существует специальная ссылка для универсальных имен, которая называется, как ни странно,
UNC. Рассмотрим пример.\\.\UNC\Server\Share\Test\Foo.txt\\?\UNC\Server\Share\Test\Foo.txtДля устройств UNCs сервер или часть общего ресурса формирует том. Например, в
\\?\server1\utilities\\filecomparer\часть сервера/общего ресурса обозначена какserver1\utilities. Это важно при вызове метода, например Path.GetFullPath(String, String), с относительными сегментами каталогов, поскольку невозможно выйти за пределы тома.
Пути к устройству DOS полностью соответствуют определению и не могут начинаться с относительного сегмента каталога (. или ..). Текущие каталоги никогда не вступают в их использование.
Пример. Способы ссылки на один и тот же файл
В следующем примере показано, как можно ссылаться на файл при использовании API в System.IO пространстве имен. Пример создает объект FileInfo и использует его свойства Name и Length для отображения имени файла и его длины.
using System;
using System.IO;
class Program
{
static void Main()
{
string[] filenames = {
@"c:\temp\test-file.txt",
@"\\127.0.0.1\c$\temp\test-file.txt",
@"\\LOCALHOST\c$\temp\test-file.txt",
@"\\.\c:\temp\test-file.txt",
@"\\?\c:\temp\test-file.txt",
@"\\.\UNC\LOCALHOST\c$\temp\test-file.txt" };
foreach (string filename in filenames)
{
FileInfo fi = new(filename);
Console.WriteLine($"file {fi.Name}: {fi.Length:N0} bytes");
}
}
}
// The example displays output like the following:
// file test-file.txt: 22 bytes
// file test-file.txt: 22 bytes
// file test-file.txt: 22 bytes
// file test-file.txt: 22 bytes
// file test-file.txt: 22 bytes
// file test-file.txt: 22 bytes
Imports System.IO
Module Program
Sub Main()
Dim filenames() As String = {
"c:\temp\test-file.txt",
"\\127.0.0.1\c$\temp\test-file.txt",
"\\LOCALHOST\c$\temp\test-file.txt",
"\\.\c:\temp\test-file.txt",
"\\?\c:\temp\test-file.txt",
"\\.\UNC\LOCALHOST\c$\temp\test-file.txt"}
For Each filename In filenames
Dim fi As New FileInfo(filename)
Console.WriteLine($"file {fi.Name}: {fi.Length:N0} bytes")
Next
End Sub
End Module
Нормализация путей
Почти все пути, передаваемые в API Windows, нормализуются. Во время нормализации Windows выполняет следующие действия:
- Определяет путь.
- Применяет текущий каталог к частично квалифицированным (относительным) путям.
- Канонизирует компоненты и разделители каталогов.
- Оценивает относительные компоненты каталога (
.для текущего каталога и..родительского каталога). - Обрезает определенные символы.
Эта нормализация происходит неявно, но это можно сделать явным образом, вызвав Path.GetFullPath метод, который упаковывает вызов функции GetFullPathName(). Вы также можете вызвать функцию Windows GetFullPathName() непосредственно с помощью P/Invoke.
Определение пути
Первым шагом нормализации пути является определение типа пути. Пути делятся на одну из нескольких категорий:
- Это пути к устройству; то есть они начинаются с двух разделителей и вопросительного знака или периода (
\\?или\\.). - Это UNC-пути; то есть они начинаются с двух разделителей без вопросительного знака или периода.
- Они являются полными путями DOS; то есть они начинаются с буквы диска, разделителя томов и разделителя компонентов (
C:\). - Они обозначают устаревшее устройство (
CON,LPT1). - Они относительны к корню текущего диска; то есть они начинаются с одного разделителя компонентов (
\). - Они относительны к текущему каталогу указанного диска; то есть они начинаются с буквы диска, разделителя томов и без разделителя компонентов (
C:). - Они относительны к текущему каталогу; то есть они начинаются с чего-либо другого (
temp\testfile.txt).
Тип пути определяет, применяется ли текущий каталог каким-то образом. Он также определяет, что такое "корень" пути.
Обработка устаревших устройств
Если путь является устаревшим устройством DOS, например CON, COM1или LPT1, оно преобразуется в путь устройства путем подготовки \\.\ и возврата.
До Windows 11 путь, начинающийся с устаревшего имени устройства, всегда интерпретируется как устаревшее устройство методом Path.GetFullPath(String) . Например, путь к устройству DOS для CON.TXT — \\.\CON, а путь к устройству DOS для COM1.TXT\file1.txt — \\.\COM1. Так как это больше не применяется к Windows 11, укажите полный путь к устаревшему устройству DOS, например \\.\CON.
Применение текущего каталога
Если путь не является полным, Windows применяет к нему текущий каталог. Пути и устройства UNC не влияют на текущий каталог. Ни полный диск с разделителем C:\ не помогает.
Если путь начинается с одного разделителя компонентов, применяется диск из текущего каталога. Например, если путь к файлу — это \utilities, а текущий каталог — это C:\temp\, нормализация создаёт C:\utilities.
Если путь начинается с буквы диска, разделителя томов и без разделителя компонентов, применяется последний текущий набор каталогов из командной оболочки для указанного диска. Если последний текущий каталог не был задан, применяется только диск. Например, если путь к файлу D:sources, текущий каталог — C:\Documents\, а предыдущий текущий каталог на диске D: был D:\sources\, результат — D:\sources\sources. Эти относительные пути диска являются общим источником ошибок логики программы и скрипта. Предполагать, что путь, начинающийся с буквы и двоеточия, не является относительным, очевидно неправильно.
Если путь начинается не с разделителя, применяется текущий диск и текущий каталог. Например, если путь - это filecompare, а текущий каталог - C:\utilities\, результатом будет C:\utilities\filecompare\.
Это важно
Относительные пути опасны в многопоточных приложениях (т. е. в большинстве приложений), так как текущий каталог является параметром для каждого процесса. Любой поток может изменять текущий каталог в любое время. Начиная с .NET Core 2.1, можно вызвать Path.GetFullPath(String, String) метод, чтобы получить абсолютный путь от относительного пути и базового пути (текущего каталога), против которого требуется разрешить его.
Канонизировать разделители
Все косые черты (/) преобразуются в стандартный разделитель Windows, обратную косую черту (\). Если они присутствуют, то ряд косых черт, которые следуют за первыми двумя косыми чертами, будет свернут в одну косую черту.
Замечание
Начиная с .NET 8 в операционных системах на основе Unix, среда выполнения больше не преобразует символы обратной косой черты (\) в разделители каталогов (символы косой черты впереди /). Дополнительные сведения см. в разделе "Отображение обратной косой черты в путях файлов Unix".
Оценка относительных компонентов
По мере обработки пути вычисляются все компоненты или сегменты, состоящие из одного или двойного периода (. или ..) :
В течение одного периода текущий сегмент удаляется, так как он относится к текущему каталогу.
В течение двойного периода текущий сегмент и родительский сегмент удаляются, так как двойной период относится к родительскому каталогу.
Родительские каталоги удаляются только в том случае, если они не выходят за пределы корня пути. Корень пути зависит от типа пути. Это диск (
C:\) для путей DOS, сервер или общий доступ для UNCs (\\Server\Share), а также префикс пути устройства для путей устройства (\\?\или\\.\).
Обрезать символы
Наряду с последовательностями разделителей и относительными сегментами, которые были удалены ранее, удаляются также некоторые дополнительные символы во время нормализации.
Если сегмент заканчивается одним периодом, этот период удаляется. (Сегмент одного или двойного периода нормализуется на предыдущем шаге. Сегмент из трех или более периодов не нормализован и фактически является допустимым именем файла или каталога.)
Если путь не заканчивается разделителем, удаляются все конечные периоды и пробелы (U+0020). Если последний сегмент является просто одним или двойным периодом, он попадает под правило относительных компонентов выше.
Это правило означает, что вы можете создать имя каталога с конечным пробелом, добавив конечный разделитель после пробела.
Это важно
Вы никогда не должны создавать каталог или имя файла с конечным пробелом. Конечные пробелы могут затруднить или сделать невозможным доступ к каталогу, и приложения часто не удаются при попытке обрабатывать каталоги или файлы, имена которых включают конечные пробелы.
Пропустить нормализацию
Обычно любой путь, передаваемый в API Windows, передается функции GetFullPathName и нормализован. Существует одно важное исключение: путь устройства, начинающийся с вопросительного знака вместо периода. Если путь не начинается точно с \\?\ (обратите внимание на использование канонической обратной косой черты), он подвергается нормализации.
Почему вы хотите пропустить нормализацию? Существует три основных причины:
Чтобы получить доступ к путям, которые обычно недоступны, но являются законными. Например, файл или каталог с именем
hidden.невозможно получить доступ никаким другим способом.Чтобы повысить производительность, не выполняя нормализацию, если нормализация уже выполнена.
Только в .NET Framework, чтобы пропустить
MAX_PATHпроверку длины пути, чтобы разрешить пути, превышающие 259 символов. Большинство API позволяют это, за исключением некоторых исключений.
Замечание
.NET Core и .NET 5+ обрабатывают длинные пути неявно и не выполняют MAX_PATH проверку. Проверка MAX_PATH применяется только к .NET Framework.
Пропуск нормализации и проверки максимального пути — это единственное различие между двумя синтаксисами пути устройства; В противном случае они идентичны. Будьте осторожны с пропуском нормализации, так как вы можете легко создавать пути, которые сложно обрабатывать обычными приложениями.
Пути, начинающиеся с \\?\ , по-прежнему нормализуются при явном передаче их функции GetFullPathName.
Вы можете передавать пути длиной более MAX_PATH символов в GetFullPathName при отсутствии \\?\. Он поддерживает произвольные пути длины до максимального размера строки, которую может обрабатывать Windows.
Регистр и файловая система Windows
Особенность файловой системы Windows, которую пользователи и разработчики других систем находят запутанной, заключается в том, что имена путей и каталогов нечувствительны к регистру. То есть имена каталогов и файлов отражают регистр строк, используемых при создании. Например, вызов метода
Directory.Create("TeStDiReCtOrY");
Directory.Create("TeStDiReCtOrY")
создает каталог с именем TeStDiReCtOrY. При переименовании каталога или файла, чтобы изменить его регистр, имя каталога или файла отражает регистр строки, используемой при переименовании. Например, следующий код переименовывает файл с именем test.txt в Test.txt:
using System.IO;
class Example3
{
static void Main()
{
var fi = new FileInfo(@".\test.txt");
fi.MoveTo(@".\Test.txt");
}
}
Imports System.IO
Module Example3
Public Sub Main()
Dim fi As New FileInfo(".\test.txt")
fi.MoveTo(".\Test.txt")
End Sub
End Module
Однако сравнение имен каталогов и файлов не учитывает регистр. Если вы ищете файл с именем "test.txt", API файловой системы .NET игнорируют регистр при сравнении. "Test.txt", "TEST.TXT", "test.TXT", а также любое другое сочетание прописных и строчных букв будет соответствовать "test.txt".