Domanda

.net ha un modo per determinare se il filesystem locale fa distinzione tra maiuscole e minuscole?

È stato utile?

Soluzione

È possibile creare un file nella cartella temporanea (utilizzando il nome file minuscolo), quindi verificare se il file esiste (utilizzando il nome file maiuscolo), ad esempio:

string file = Path.GetTempPath() + Guid.NewGuid().ToString().ToLower();
File.CreateText(file).Close();
bool isCaseInsensitive = File.Exists(file.ToUpper());
File.Delete(file);

Altri suggerimenti

Non esiste una tale funzione nella libreria di classi .NET.

Puoi, tuttavia, implementare il tuo: prova a creare un file con un nome minuscolo e quindi prova ad aprirlo con la versione maiuscola del suo nome. Probabilmente è possibile migliorare questo metodo, ma hai capito.

MODIFICA : in realtà potresti semplicemente prendere il primo file nella directory principale e quindi controllare se esistono sia il nome file.ToLower () che il nome file.ToUpper (). Sfortunatamente è del tutto possibile che esistano varianti sia maiuscole che minuscole dello stesso file, quindi è necessario confrontare le proprietà FileInfo.Name di entrambe le varianti minuscole e maiuscole per vedere se sono effettivamente uguali o meno. Ciò non richiederà la scrittura sul disco.

Ovviamente, questo fallirà se non ci sono file sul volume. In questo caso, torna alla prima opzione (vedi la risposta di Martin per l'implementazione).

Tieni presente che potresti avere più file system con regole di casing diverse. Ad esempio, il filesystem di root potrebbe fare distinzione tra maiuscole e minuscole, ma è possibile avere un filesystem senza distinzione tra maiuscole e minuscole (ad es. Una chiavetta USB con un filesystem FAT su di esso) montato da qualche parte. Quindi, se esegui tali controlli, assicurati di inserirli nella directory a cui accederai.

Inoltre, cosa succede se l'utente copia i dati da un maiuscolo / minuscolo a un file system senza distinzione tra maiuscole e minuscole? Se hai file che differiscono solo per caso, uno sovrascriverà l'altro, causando la perdita di dati. Quando si copia nell'altra direzione, è possibile che si verifichino problemi, ad esempio, se il file A contiene un riferimento al file "b", ma il file in realtà è denominato "B". Funziona sul file system originale senza distinzione tra maiuscole e minuscole, ma non sul sistema con distinzione tra maiuscole e minuscole.

Quindi suggerirei di evitare, a seconda che il file system sia sensibile al maiuscolo / minuscolo, se è possibile. Non generare nomi di file che differiscono solo per caso, utilizzare le finestre di dialogo di selezione file standard, essere pronti a cambiare il caso, ecc.

Prova a creare un file temporaneo in minuscolo, quindi controlla se esiste utilizzando il maiuscolo.

Non è una funzione .NET, ma le funzioni GetVolumeInformation e GetVolumeInformationByHandleW dell'API di Windows faranno ciò che desideri (vedi il parametro lpFileSystemFlags)

Esistono in realtà due modi per interpretare la domanda originale.

  1. Come determinare se un file system specifico è in grado di preservare la distinzione tra maiuscole e minuscole nei nomi dei file?
  2. Come determinare se il sistema operativo corrente interpreta i nomi dei file con distinzione tra maiuscole e minuscole quando si lavora con un file system specifico.

Questa risposta si basa sulla seconda interpretazione, perché penso che questo sia ciò che l'OP voleva sapere e anche ciò che conta per la maggior parte delle persone.

Il seguente codice è vagamente basato sulla risposta di M4N e Nicolas Raoul e tenta di creare un'implementazione davvero solida in grado di determinare se il sistema operativo gestisce i nomi dei file con distinzione tra maiuscole e minuscole all'interno di una directory specificata (escluse le sottodirectory, poiché questi potrebbero essere montati da un altro file system).

Funziona creando due nuovi file in successione, uno con lettere minuscole, l'altro con caratteri maiuscoli. I file vengono bloccati esclusivamente e vengono eliminati automaticamente quando vengono chiusi. Ciò dovrebbe evitare eventuali effetti collaterali negativi causati dalla creazione di file. Naturalmente, questa implementazione funziona solo se esiste la directory specificata e l'utente corrente è in grado di creare file al suo interno.

Il codice è scritto per .NET Framework 4.0 e C # 7.2 (o successivo).

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

/// <summary>
/// Check whether the operating system handles file names case-sensitive in the specified directory.
/// </summary>
/// <param name="directoryPath">The path to the directory to check.</param>
/// <returns>A value indicating whether the operating system handles file names case-sensitive in the specified directory.</returns>
/// <exception cref="ArgumentNullException"><paramref name="directoryPath"/> is null.</exception>
/// <exception cref="ArgumentException"><paramref name="directoryPath"/> contains one or more invalid characters.</exception>
/// <exception cref="DirectoryNotFoundException">The specified directory does not exist.</exception>
/// <exception cref="UnauthorizedAccessException">The current user has no write permission to the specified directory.</exception>
private static bool IsFileSystemCaseSensitive(string directoryPath)
{
    if (directoryPath == null)
    {
        throw new ArgumentNullException(nameof(directoryPath));
    }

    while (true)
    {
        string fileNameLower = ".cstest." + Guid.NewGuid().ToString();
        string fileNameUpper = fileNameLower.ToUpperInvariant();

        string filePathLower = Path.Combine(directoryPath, fileNameLower);
        string filePathUpper = Path.Combine(directoryPath, fileNameUpper);

        FileStream fileStreamLower = null;
        FileStream fileStreamUpper = null;
        try
        {
            try
            {
                // Try to create filePathUpper to ensure a unique non-existing file.
                fileStreamUpper = new FileStream(filePathUpper, FileMode.CreateNew, FileAccess.Write, FileShare.None, bufferSize: 4096, FileOptions.DeleteOnClose);

                // After ensuring that it didn't exist before, filePathUpper must be closed/deleted again to ensure correct opening of filePathLower, regardless of the case-sensitivity of the file system.
                // On case-sensitive file systems there is a tiny chance for a race condition, where another process could create filePathUpper between closing/deleting it here and newly creating it after filePathLower.
                // This method would then incorrectly indicate a case-insensitive file system.
                fileStreamUpper.Dispose();
            }
            catch (IOException ioException) when (IsErrorFileExists(ioException))
            {
                // filePathUpper already exists, try another file name
                continue;
            }

            try
            {
                fileStreamLower = new FileStream(filePathLower, FileMode.CreateNew, FileAccess.Write, FileShare.None, bufferSize: 4096, FileOptions.DeleteOnClose);
            }
            catch (IOException ioException) when (IsErrorFileExists(ioException))
            {
                // filePathLower already exists, try another file name
                continue;
            }

            try
            {
                fileStreamUpper = new FileStream(filePathUpper, FileMode.CreateNew, FileAccess.Write, FileShare.None, bufferSize: 4096, FileOptions.DeleteOnClose);

                // filePathUpper does not exist, this indicates case-sensitivity
                return true;
            }
            catch (IOException ioException) when (IsErrorFileExists(ioException))
            {
                // fileNameUpper already exists, this indicates case-insensitivity
                return false;
            }
        }
        finally
        {
            fileStreamLower?.Dispose();
            fileStreamUpper?.Dispose();
        }
    }
}

/// <summary>
/// Determines whether the specified <see cref="IOException"/> indicates a "file exists" error.
/// </summary>
/// <param name="ioException">The <see cref="IOException"/> to check.</param>
/// <returns>A value indicating whether the specified <see cref="IOException"/> indicates a "file exists" error.</returns>
private static bool IsErrorFileExists(IOException ioException)
{
    // https://referencesource.microsoft.com/mscorlib/microsoft/win32/win32native.cs.html#dd35d7f626262141
    const int ERROR_FILE_EXISTS = 0x50;

    // The Exception.HResult property's get accessor is protected before .NET 4.5, need to get its value via reflection.
    int hresult = (int)typeof(Exception)
        .GetProperty("HResult", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
        .GetValue(ioException, index: null);

    // https://referencesource.microsoft.com/mscorlib/microsoft/win32/win32native.cs.html#9f6ca3226ff8f9ba
    return hresult == unchecked((int)0x80070000 | ERROR_FILE_EXISTS);
}

Come puoi vedere c'è una piccola possibilità per una condizione di razza che può causare un falso negativo. Se questa condizione di competizione è qualcosa di cui ti preoccupi davvero, ti suggerisco di fare il controllo una seconda volta quando il risultato è falso, sia all'interno del metodo IsFileSystemCaseSensitive o al di fuori di esso. Tuttavia, a mio avviso, la probabilità di incontrare una volta questa condizione di razza, figuriamoci due volte di seguito, è astronomicamente piccola.

Invoco The Cheat:

Path.DirectorySeparatorChar == '\\' ? "I'm insensitive" : "I'm probably sensitive"
/// <summary>
/// Check whether the operating system is case-sensitive.
/// For instance on Linux you can have two files/folders called
//// "test" and "TEST", but on Windows the two can not coexist.
/// This method does not extend to mounted filesystems, which might have different properties.
/// </summary>
/// <returns>true if the operating system is case-sensitive</returns>
public static bool IsFileSystemCaseSensitive()
{
    // Actually try.
    string file = Path.GetTempPath() + Guid.NewGuid().ToString().ToLower() + "test";
    File.CreateText(file).Close();
    bool result = ! File.Exists(file.ToUpper());
    File.Delete(file);

    return result;
}

Basato sulla risposta di M4N, con le seguenti modifiche:

  • Nomi statici in modo che siamo sicuri che contenga una lettera e non solo numeri.
  • Forse più leggibile?
  • Avvolto in un metodo.
  • Documentazione.

Una strategia migliore sarebbe quella di prendere un percorso come argomento e creare il file sullo stesso filesystem, ma scrivere lì potrebbe avere conseguenze inaspettate.

Che ne dici di questa euristica?

public static bool IsCaseSensitiveFileSystem() {
   var tmp = Path.GetTempPath();
   return !Directory.Exists(tmp.ToUpper()) || !Directory.Exists(tmp.ToLower());
}

Ecco l'approccio che non utilizza file temporanei:

using System;
using System.Runtime.InteropServices;

static bool IsCaseSensitive()
{
    if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ||
        RuntimeInformation.IsOSPlatform(OSPlatform.OSX))  // HFS+ (the Mac file-system) is usually configured to be case insensitive.
    {
        return false;
    }
    else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
    {
        return true;
    }
    else if (Environment.OSVersion.Platform == PlatformID.Unix)
    {
        return true;
    }
    else
    {
       // A default.
       return false;
    }
}

Al contrario, contiene una conoscenza radicata degli ambienti operativi.

Prontamente disponibile come pacchetto NuGet, funziona su tutto .NET 4.0+ e aggiornato regolarmente: https://github.com/gapotchenko/Gapotchenko.FX/tree/master/Source/Gapotchenko.FX.IO#iscasesensitive

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top