다음을 통해 공유


Windows 시스템의 파일 경로 형식

네임스페이스에 있는 System.IO 많은 형식의 멤버에는 파일 시스템 리소스에 path 대한 절대 또는 상대 경로를 지정할 수 있는 매개 변수가 포함됩니다. 그런 다음 이 경로가 Windows 파일 시스템 API에 전달됩니다. 이 항목에서는 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/FQDN 주소(IPv4 및 v6 지원됨)일 수 있습니다.
  • 공유 이름입니다. 이 이름은 호스트 이름 \에서 .로 구분됩니다. 서버와 공유 이름이 함께 볼륨을 구성합니다.
  • 디렉터리 이름입니다. 디렉터리 구분 기호 문자는 중첩된 디렉터리 계층 내의 하위 디렉터리를 구분합니다.
  • 선택적 파일 이름입니다. 디렉터리 구분 기호 문자는 파일 경로와 파일 이름을 구분합니다.

다음은 UNC 경로의 몇 가지 예입니다.

경로 설명
\\system07\C$\ C:system07 드라이브 루트 디렉터리입니다.
\\Server2\Share\Test\Foo.txt Foo.txt 볼륨의 Test 디렉터리에 있는 \\Server2\Share 파일.

UNC 경로는 항상 완전히 지정되어야 합니다. 상대 디렉터리 세그먼트(...)를 포함할 수 있지만 정규화된 경로의 일부여야 합니다. UNC 경로를 드라이브 문자에 매핑해야만 상대 경로를 사용할 수 있습니다.

DOS 디바이스 경로

Windows 운영 체제에는 파일을 포함한 모든 리소스를 가리키는 통합 개체 모델이 있습니다. 이러한 개체 경로는 콘솔 창에서 액세스할 수 있으며 레거시 DOS 및 UNC 경로가 매핑되는 기호화된 링크의 특수 폴더를 통해 Win32 계층에 노출됩니다. 이 특수 폴더는 다음 중 하나인 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 Core 1.1 및 .NET Framework 4.6.2부터 Windows에서 실행되는 .NET 구현에서 지원됩니다.

DOS 디바이스 경로는 다음 구성 요소로 구성됩니다.

  • 경로를 DOS 디바이스 경로로 식별하는 디바이스 경로 지정자(\\.\ 또는 \\?\)입니다.

    비고

    모든 \\?\ 버전의 .NET Core 및 .NET 5 이상 및 버전 4.6.2부터 .NET Framework에서 지원됩니다.

  • "실제" 디바이스 개체에 대한 기호 링크(C: 드라이브 이름의 경우 또는 볼륨 GUID의 경우 Volume{b75e2c83-0000-0000-0000-602f00000000}).

    디바이스 경로 지정자가 볼륨 또는 드라이브를 식별한 후 DOS 디바이스 경로의 첫 번째 세그먼트입니다. (예: \\?\C:\\\.\BootPartition\.)

    특정 UNC에 대한 링크가 있으며, 그 이름은 당연히 UNC입니다. 다음은 그 예입니다.

    \\.\UNC\Server\Share\Test\Foo.txt \\?\UNC\Server\Share\Test\Foo.txt

    디바이스 UNC의 경우 서버/공유 부분이 볼륨을 형성합니다. \\?\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

경로 표준화

Windows API에 전달된 거의 모든 경로가 정규화됩니다. 정규화 중에 Windows는 다음 단계를 수행합니다.

  • 경로를 식별합니다.
  • 현재 디렉터리를 부분적으로 정규화된(상대) 경로에 적용합니다.
  • 구성 요소 및 디렉터리 구분 기호를 정식화합니다.
  • 상대 디렉터리 구성 요소(. 현재 디렉터리 및 .. 부모 디렉터리의 경우)를 평가합니다.
  • 특정 문자를 잘라냅니다.

이 정규화는 암시적으로 수행되지만 Path.GetFullPath에 대한 호출을 래핑하는 메서드를 호출 하여 명시적으로 수행할 수 있습니다. P/Invoke를 사용하여 Windows GetFullPathName() 함수 를 직접 호출할 수도 있습니다.

경로 식별

경로 정규화의 첫 번째 단계는 경로 유형을 식별하는 것입니다. 경로는 다음과 같은 몇 가지 범주 중 하나로 분류됩니다.

  • 디바이스 경로입니다. 즉, 두 구분 기호와 물음표 또는 마침표(\\? 또는 \\.)로 시작합니다.
  • UNC 경로입니다. 즉, 물음표나 마침표 없이 두 구분 기호로 시작합니다.
  • 정규화된 DOS 경로입니다. 즉, 드라이브 문자, 볼륨 구분 기호 및 구성 요소 구분 기호(C:\)로 시작합니다.
  • 레거시 디바이스(CON, LPT1)를 지정합니다.
  • 현재 드라이브의 루트를 기준으로 합니다. 즉, 단일 구성 요소 구분 기호(\)로 시작합니다.
  • 지정된 드라이브의 현재 디렉터리에 상대적입니다. 즉, 드라이브 문자로 시작하고 볼륨 구분 기호가 있으며 구성 요소 구분 기호(C:)는 없습니다.
  • 현재 디렉터리를 기준으로 합니다. 즉, 다른 항목(temp\testfile.txt)으로 시작합니다.

경로의 형식은 현재 디렉터리가 어떤 방식으로 적용되는지 여부를 결정합니다. 경로의 "루트"도 결정합니다.

레거시 디바이스 처리

경로가 레거시 DOS 디바이스(예: CON또는COM1LPT1)인 경우 앞에 추가하여 \\.\ 디바이스 경로로 변환되고 반환됩니다.

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 구분 기호인 백슬래시(\)로 변환됩니다. 첫 두 슬래시 뒤에 오는 일련의 슬래시가 있을 경우, 그것들은 단일 슬래시로 축소됩니다.

비고

Unix 기반 운영 체제의 .NET 8부터 런타임은 더 이상 백 슬래시(\) 문자를 디렉터리 구분 기호(슬래시 /)로 변환하지 않습니다. 자세한 내용은 Unix 파일 경로의 백슬래시 매핑을 참조하세요.

상대 구성 요소 평가

경로가 처리되면 단일 또는 이중 기간(. 또는)으로 구성된 모든 구성 요소 또는 ..세그먼트가 평가됩니다.

  • 단일 기간 동안 현재 세그먼트는 현재 디렉터리를 참조하므로 제거됩니다.

  • 이중 기간의 경우 이중 기간이 부모 디렉터리를 참조하므로 현재 세그먼트와 부모 세그먼트가 제거됩니다.

    부모 디렉터리는 경로의 루트를 초과하지 않는 경우에만 제거됩니다. 경로의 루트는 경로의 형식에 따라 달라집니다. DOS 경로의 드라이브(C:\), UNC의 서버/공유(\\Server\Share), 그리고 디바이스 경로(\\?\ 또는 \\.\)의 디바이스 경로 접두사입니다.

문자 자르기

앞서 제거된 구분 기호 및 상대 세그먼트의 실행과 함께 정규화 중에 일부 추가 문자가 제거됩니다.

  • 세그먼트가 단일 마침표로 끝나면 해당 기간이 제거됩니다. 단일 또는 이중 마침표의 세그먼트는 이전 단계에서 정규화됩니다. 세 개 이상의 마침표 세그먼트는 정규화되지 않으며, 실제로 정상적인 파일 또는 디렉터리 이름으로 간주됩니다.

  • 경로가 구분 기호로 끝나지 않으면 모든 후행 마침표와 공백(U+0020)이 제거됩니다. 마지막 세그먼트가 단순히 단일 또는 이중 기간인 경우 위의 상대 구성 요소 규칙에 속합니다.

    이 규칙은 공백 뒤에 후행 구분 기호를 추가하여 후행 공백이 있는 디렉터리 이름을 만들 수 있음을 의미합니다.

    중요합니다

    후행 공백이 있는 디렉터리 또는 파일 이름을 만들면 안 됩니다. 후행 공백을 사용하면 디렉터리에 액세스하기가 어렵거나 불가능할 수 있으며, 이름에 후행 공백이 포함된 디렉터리 또는 파일을 처리하려고 할 때 애플리케이션이 일반적으로 실패합니다.

정규화 건너뛰기

일반적으로 Windows API에 전달된 모든 경로는 GetFullPathName 함수 에 (효과적으로) 전달되고 정규화됩니다. 한 가지 중요한 예외가 있습니다. 마침표 대신 물음표로 시작하는 디바이스 경로입니다. 경로가 정확히 \\?\ 시작되지 않는 한(정식 백슬래시 사용 참고) 정규화됩니다.

정규화를 건너뛰려는 이유는 무엇인가요? 세 가지 주요 이유가 있습니다.

  1. 일반적으로 사용할 수 없지만 합법적인 경로에 액세스하려면 예를 들어 다른 방법으로는 액세스할 수 없는 파일 또는 디렉터리입니다 hidden..

  2. 이미 정규화된 경우 정규화를 건너뛰어 성능을 향상시킵니다.

  3. .NET Framework에서만, 경로 길이 확인MAX_PATH을 생략하여 259자보다 큰 경로를 허용합니다. 대부분의 API는 일부 예외를 제외하고 이를 허용합니다.

비고

.NET Core 및 .NET 5+는 긴 경로를 암시적으로 처리하며 MAX_PATH 검사를 수행하지 않습니다. 검사는 MAX_PATH .NET Framework에만 적용됩니다.

정규화 및 최대 경로 검사를 건너뛰는 것은 두 디바이스 경로 구문 간의 유일한 차이점입니다. 그렇지 않으면 동일합니다. "일반" 애플리케이션에서 처리하기 어려운 경로를 쉽게 만들 수 있으므로 정규화를 건너뛰는 데 주의해야 합니다.

\\?\로 시작하는 경로는 GetFullPathName 함수에 명시적으로 전달할 때 여전히 정규화됩니다.

을 사용하지 않고MAX_PATH도 여러 문자의 경로를 \\?\에 전달할 수 있습니다. Windows에서 처리할 수 있는 최대 문자열 크기까지 임의의 길이 경로를 지원합니다.

사례 및 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"라는 파일을 검색할 때, .NET 파일 시스템 API들은 대소문자를 구분하지 않고 비교합니다. "Test.txt", "TEST.TXT", "test.TXT" 및 대문자와 소문자의 다른 조합은 "test.txt"와 일치합니다.