Formati dei percorsi di file nei sistemi Windows

I membri di molti tipi nello spazio dei nomi System.IO includono un parametro path che consente di specificare un percorso assoluto o relativo di una risorsa del file system. Questo percorso viene quindi passato alle API del file system Windows. Questo argomento descrive i formati per i percorsi di file che è possibile usare nei sistemi Windows.

Percorsi DOS tradizionali

Un percorso DOS standard può essere costituito da tre componenti:

Se sono presenti tutti e tre i componenti, il percorso è assoluto. Se non si specifica alcun volume o lettera di unità e il nome della directory inizia con il carattere separatore di directory, il percorso è relativo dalla radice dell'unità corrente. In caso contrario, il percorso è relativo alla directory corrente. La tabella seguente illustra alcuni percorsi possibili di directory e file.

Percorso Descrizione
C:\Documents\Newsletters\Summer2018.pdf Percorso file assoluto dalla radice dell'unità C:.
\Program Files\Custom Utilities\StringFinder.exe Percorso relativo dalla radice dell'unità corrente.
2018\January.xlsx Percorso relativo a un file in una sottodirectory della directory corrente.
..\Publications\TravelBrochure.pdf Percorso relativo di un file in una directory a partire dalla directory corrente.
C:\Projects\apilibrary\apilibrary.sln Percorso assoluto a un file dalla radice dell'unità C:.
C:Projects\apilibrary\apilibrary.sln Percorso relativo dalla directory corrente dell'unità C:.

Importante

Si noti la differenza tra gli ultimi due percorsi. Entrambi specificano l'identificatore di volume facoltativo (C: in entrambi i casi), ma il primo inizia con la radice del volume specificato, mentre il secondo no. Di conseguenza, il primo è un percorso assoluto dalla directory radice dell'unità C:, mentre il secondo è un percorso relativo dalla directory corrente dell'unità C:. L'uso del secondo formato quando si intende usare il primo è una fonte comune di bug per i percorsi di file Windows.

È possibile determinare se un percorso di file è completo (ovvero se il percorso è indipendente dalla directory corrente e non cambia quando la directory corrente viene modificata) chiamando il metodo Path.IsPathFullyQualified. Si noti che tale percorso può includere segmenti di directory relativi (. e ..) ed essere comunque completo se il percorso risolto punta sempre alla stessa posizione.

L'esempio seguente illustra la differenza fra percorsi assoluti e relativi. Si presuppone che la directory D:\FY2018\ esista già e che non sia stata impostata una directory corrente per D:\ dal prompt dei comandi prima di eseguire l'esempio.

using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;

public class Example
{
   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 Uri(Assembly.GetExecutingAssembly().GetName().CodeBase, UriKind.Absolute);
         string commandLine = $"/C cd D:\\FY2018 & \"{currentExe.LocalPath}\" stop";
         ProcessStartInfo psi = new ProcessStartInfo("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.Diagnostics
Imports System.IO
Imports System.Reflection

Public Module Example

    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

Percorsi UNC

I percorsi UNC (Universal Naming Convention), che vengono usati per accedere alle risorse di rete, hanno il formato seguente:

  • Un nome host o server, preceduto da \\. Il nome del server può essere un nome computer NetBIOS o un indirizzo IP/FQDN (sono supportati IPv4 e v6).
  • Un nome condivisione, separato dal nome host da \. Insieme, il server e il nome condivisione costituiscono il volume.
  • Nome di directory. Il carattere separatore di directory separa le sottodirectory all'interno della gerarchia di directory annidata.
  • Un nome file facoltativo. Il carattere separatore di directory separa il percorso e il nome del file.

Di seguito sono riportati alcuni esempi di percorsi UNC:

Percorso Descrizione
\\system07\C$\ Directory radice dell'unità C: in system07.
\\Server2\Share\Test\Foo.txt Il file Foo.txt nella directory Test del volume \\Server2\Share.

I percorsi UNC devono essere sempre completi. Possono includere segmenti di directory relativi (. e ..), ma devono far parte di un percorso completo. È possibile usare i percorsi relativi solo eseguendo il mapping di un percorso UNC a una lettera di unità.

Percorsi del dispositivo DOS

Il sistema operativo Windows ha un modello a oggetti unificato che punta a tutte le risorse, inclusi i file. Questi percorsi degli oggetti sono accessibili dalla finestra della console e vengono esposti al livello Win32 tramite una cartella speciale di collegamenti simbolici a cui sono mappati i percorsi UNC e DOS legacy. Questa cartella speciale è accessibile tramite la sintassi del percorso del dispositivo DOS, ovvero uno dei due riportati di seguito:

\\.\C:\Test\Foo.txt \\?\C:\Test\Foo.txt

Oltre a identificare un'unità con la relativa lettera di unità, è possibile identificare un volume usando l'identificatore univoco globale (GUID) del volume. Assume il formato:

\\.\Volume{b75e2c83-0000-0000-0000-602f00000000}\Test\Foo.txt \\?\Volume{b75e2c83-0000-0000-0000-602f00000000}\Test\Foo.txt

Nota

La sintassi del percorso del dispositivo DOS è supportata nelle implementazioni .NET in esecuzione in Windows a partire da .NET Core 1.1 e .NET Framework 4.6.2.

Il percorso del dispositivo DOS è costituito dai componenti seguenti:

  • L'identificatore del percorso del dispositivo (\\.\ o \\?\), che identifica il percorso come percorso di un dispositivo DOS.

    Nota

    \\?\ è supportato in tutte le versioni di .NET Core, in .NET 5+ e in .NET Framework a partire dalla versione 4.6.2.

  • Un collegamento simbolico all'oggetto dispositivo "reale" (C: nel caso di un nome di unità o Volume{b75e2c83-0000-0000-0000-602f00000000} nel caso di un GUID del volume).

    Il primo segmento del percorso del dispositivo DOS dopo l'identificatore del percorso del dispositivo identifica il volume o l'unità (ad esempio, \\?\C:\ e \\.\BootPartition\).

    È disponibile un collegamento specifico per i percorsi UNC, chiamato UNC. Ad esempio:

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

    Per i percorsi UNC del dispositivo, la parte server/condivisione costituisce il volume. Ad esempio, in \\?\server1\utilities\\filecomparer\, la parte server/condivisione è server1\utilities. Ciò risulta particolarmente importante quando si chiama un metodo come Path.GetFullPath(String, String) con segmenti di directory relativi; non è mai possibile andare oltre il volume.

I percorsi dei dispositivi DOS sono completi per definizione e non possono iniziare con un segmento di directory relativo (. o ..). Le directory correnti non entrano mai in uso.

Esempio: modi per fare riferimento allo stesso file

L'esempio seguente illustra alcuni dei modi in cui è possibile fare riferimento a un file quando si usano le API nello spazio dei nomi System.IO. L'esempio crea un'istanza di un oggetto FileInfo e usa le rispettive proprietà Name e Length per visualizzare il nome e la lunghezza del file.

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",
            @"\\127.0.0.1\c$\temp\test-file.txt" };

        foreach (var filename in filenames)
        {
            FileInfo fi = new FileInfo(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
//      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",
                "\\127.0.0.1\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

Normalizzazione del percorso

Quasi tutti i percorsi passati alle API di Windows sono normalizzati. Durante la normalizzazione, Windows esegue la procedura seguente:

  • Identifica il percorso.
  • Applica la directory corrente ai percorsi parzialmente completi (relativi).
  • Converte in forma canonica i separatori di directory e componenti.
  • Valuta i componenti di directory relativi (. per la directory corrente e .. per la directory padre).
  • Elimina determinati caratteri.

Questa normalizzazione avviene in modo implicito, ma è possibile eseguirla in modo esplicito chiamando il metodo Path.GetFullPath, che esegue il wrapping di una chiamata alla funzione GetFullPathName(). È anche possibile chiamare la funzione GetFullPathName() di Windows direttamente tramite P/Invoke.

Identificare il percorso

Il primo passaggio nella normalizzazione del percorso consiste nell'identificare il tipo di percorso. I percorsi rientrano in una delle categorie seguenti:

  • Sono percorsi del dispositivo, vale a dire iniziano con due separatori e un punto interrogativo o un punto (\\? o \\.).
  • Sono percorsi UNC, vale a dire iniziano con due separatori senza un punto interrogativo o un punto.
  • Sono percorsi DOS completi, vale a dire iniziano con una lettera di unità, un separatore di volume e un separatore di componenti (C:\).
  • Definiscono un dispositivo legacy (CON, LPT1).
  • Sono relativi alla radice dell'unità corrente, vale a dire iniziano con un separatore di componenti singolo (\).
  • Sono relativi alla directory corrente di un'unità specificata, vale a dire iniziano con una lettera di unità, un separatore di volume e nessun separatore di componenti (C:).
  • Sono relativi alla directory corrente, vale a dire iniziano con qualsiasi altro elemento (temp\testfile.txt).

Il tipo di percorso determina se una directory corrente viene applicata o meno in qualche modo. Definisce inoltre qual è la "radice" del percorso.

Gestire i dispositivi legacy

Se il percorso è un dispositivo DOS legacy come CON, COM1 o LPT1, viene convertito in un percorso del dispositivo anteponendo \\.\ e viene restituito.

Un percorso che inizia con il nome di un dispositivo legacy viene sempre interpretato come dispositivo legacy dal metodo Path.GetFullPath(String). Ad esempio, il percorso del dispositivo DOS per CON.TXT è \\.\CON e il percorso del dispositivo DOS per COM1.TXT\file1.txt è \\.\COM1.

Applicare la directory corrente

Se un percorso non è completo, Windows applica la directory corrente. I percorsi UNC e dei dispositivi non hanno la directory corrente applicata Neanche un'unità completa con separatore C:\.

Se il percorso inizia con un separatore di componenti singolo, viene applicata l'unità della directory corrente. Ad esempio, se il percorso del file è \utilities e la directory corrente è C:\temp\, la normalizzazione produce C:\utilities.

Se il percorso inizia con una lettera di unità, un separatore di volume e nessun separatore di componenti, viene applicata l'ultima directory corrente impostata dalla shell dei comandi per l'unità specificata. Se l'ultima directory corrente non è stata impostata, viene applicata solo l'unità. Ad esempio, se il percorso del file è D:sources, la directory corrente è C:\Documents\ e l'ultima directory corrente nell'unità D: è stata D:\sources\, il risultato è D:\sources\sources. Questi percorsi "relativi di unità" sono una fonte comune di errori logici per script e programmi. Presupporre che un percorso che inizia con una lettera e i due punti non sia relativo ovviamente non è corretto.

Se il percorso inizia con un elemento diverso da un separatore, vengono applicate l'unità e la directory correnti. Ad esempio, se il percorso è filecompare e la directory corrente è C:\utilities\, il risultato è C:\utilities\filecompare\.

Importante

I percorsi relativi sono pericolosi nelle applicazioni multithreading (vale a dire, la maggior parte delle applicazioni) perché la directory corrente è un'impostazione specifica per il processo. Qualsiasi thread può modificare la directory corrente in qualsiasi momento. A partire da .NET Core 2.1, è possibile chiamare il metodo Path.GetFullPath(String, String) per ottenere un percorso assoluto da un percorso relativo e il percorso base (directory corrente) in base a cui si procede alla risoluzione.

Rendere canonici i separatori

Tutte le barre (/) sono convertite nel separatore di Windows standard, la barra rovesciata (\). Se sono presenti, una serie di barre che seguono le prime due vengono compresse in una singola barra.

Valutare i componenti relativi

Mentre il percorso viene elaborato, vengono valutati tutti i componenti o i segmenti costituiti da un punto singolo o doppio (. o ..):

  • Nel caso di un punto singolo, il segmento corrente viene rimosso, perché fa riferimento alla directory corrente.

  • Nel caso di un punto doppio, il segmento corrente e quello padre vengono rimossi, perché il punto doppio fa riferimento alla directory padre.

    Le directory padre vengono rimosse solo se non sono oltre la radice del percorso. La radice del percorso dipende dal tipo di percorso. Si tratta dell'unità (C:\) per i percorsi DOS, del server/condivisione per i percorsi UNC (\\Server\Share) e del prefisso del percorso del dispositivo per i percorsi del dispositivo (\\?\ o \\.\).

Tagliare i caratteri

Insieme ai separatori e ai segmenti relativi rimossi in precedenza, durante la normalizzazione vengono rimossi alcuni caratteri aggiuntivi:

  • Se un segmento termina con un punto singolo, il punto viene rimosso (un segmento di un punto singolo o doppio è normalizzato nel passaggio precedente; un segmento di tre o più punti non è normalizzato ed è in realtà un nome di file/directory valido).

  • Se il percorso non termina con un separatore, tutti gli spazi e i punti finali (U+0020) vengono rimossi. Se l'ultimo segmento è semplicemente un punto singolo o doppio, rientra nella regola dei componenti relativi esposta in precedenza.

    Questa regola indica che è possibile creare un nome di directory con uno spazio finale mediante l'aggiunta di un separatore finale dopo lo spazio.

    Importante

    Non creare mai una directory o un nome file con uno spazio finale. Gli spazi finali possono rendere difficile o impossibile l'accesso a una directory e le applicazioni riscontrano in genere un errore quando si tenta di gestire directory o file i cui nomi includono spazi finali.

Ignorare la normalizzazione

Normalmente, qualsiasi percorso passato a un'API di Windows viene effettivamente passato alla funzione GetFullPathName e normalizzato. Un'eccezione degna di nota è quando il percorso di un dispositivo inizia con un punto interrogativo anziché con un punto. A meno che il percorso non inizi esattamente con \\?\ (si noti l'uso della barra rovesciata canonica), viene normalizzato.

Perché escludere la normalizzazione? Per tre motivi principali:

  1. Per ottenere l'accesso a percorsi che non sono in genere disponibili ma sono validi. A un file o una directory denominata hidden., ad esempio, è impossibile accedere in qualsiasi altro modo.

  2. Per migliorare le prestazioni escludendo la normalizzazione se è già stata eseguita.

  3. Solo in .NET Framework, per ignorare il controllo MAX_PATH per la lunghezza del percorso in modo da consentire percorsi contenenti più di 259 caratteri. La maggior parte delle API consente questa operazione, con alcune eccezioni.

Nota

.NET Core e .NET 5+ gestiscono i percorsi lunghi in modo implicito e non esegue un controllo MAX_PATH. Il controllo MAX_PATH si applica solo a .NET Framework.

L'esclusione della normalizzazione e dei controlli MAX_PATH è l'unica differenza tra le sintassi dei due percorsi di dispositivo; altrimenti sono identici. Prestare attenzione quando si esclude la normalizzazione, perché si rischia di creare percorsi difficili da gestire con le "normali" applicazioni.

I percorsi che iniziano con \\?\ vengono comunque normalizzati se si passano in modo esplicito alla funzione GetFullPathName.

Si noti che è possibile passare percorsi contenenti più di MAX_PATH caratteri a GetFullPathName senza \\?\. Supporta percorsi di lunghezza arbitraria fino alla dimensione massima delle stringhe che Windows è in grado di gestire.

Maiuscole/minuscole e file system di Windows

Una peculiarità del file system di Windows che può confondere gli utenti e gli sviluppatori non Windows è che i nomi di percorsi e directory non fanno distinzione tra maiuscole e minuscole. In altre parole, i nomi di file e directory riflettono l'uso di maiuscole/minuscole delle stringhe usate quando sono stati creati. Ad esempio, la chiamata al metodo

Directory.Create("TeStDiReCtOrY");
Directory.Create("TeStDiReCtOrY")

crea una directory denominata TeStDiReCtOrY. Se si rinomina una directory o un file per modificare l'uso di maiuscole/minuscole, il nome del file o della directory riflette le maiuscole/minuscole usate quando è stato rinominato. Ad esempio, il codice seguente rinomina un file denominato test.txt in Test.txt:

using System.IO;

class Example
{
   static void Main()
   {
      var fi = new FileInfo(@".\test.txt");
      fi.MoveTo(@".\Test.txt");
   }
}
Imports System.IO

Module Example
    Public Sub Main()
        Dim fi As New FileInfo(".\test.txt")
        fi.MoveTo(".\Test.txt")
    End Sub
End Module

Tuttavia, nei confronti tra i nomi di file e directory non viene fatta distinzione tra maiuscole e minuscole. Se si cerca un file denominato "test.txt", le API del file system .NET non fanno distinzione tra maiuscole e minuscole nel confronto. “Test.txt, TEST.TXT, test.TXT” e qualsiasi altra combinazione di lettere maiuscole e minuscole corrisponderanno a "test.txt".