Perché Path.Combine non concatena correttamente i nomi di file che iniziano con Path.DirectorySeparatorChar?

StackOverflow https://stackoverflow.com/questions/53102

  •  09-06-2019
  •  | 
  •  

Domanda

Dal Finestra immediata in Visual Studio:

> Path.Combine(@"C:\x", "y")
"C:\\x\\y"
> Path.Combine(@"C:\x", @"\y")
"\\y"

Sembra che dovrebbero essere entrambi uguali.

Il vecchio FileSystemObject.BuildPath() non funzionava in questo modo...

È stato utile?

Soluzione

Questa è una specie di domanda filosofica (a cui forse solo Microsoft può veramente rispondere), poiché fa esattamente ciò che dice la documentazione.

System.IO.Path.Combine

"Se path2 contiene un percorso assoluto, questo metodo restituisce path2."

Ecco l'effettivo metodo Combina dall'origine .NET.Puoi vedere che chiama CombinaNoControlli, che poi chiama IsPathRooted su path2 e restituisce quel percorso in tal caso:

public static String Combine(String path1, String path2) {
    if (path1==null || path2==null)
        throw new ArgumentNullException((path1==null) ? "path1" : "path2");
    Contract.EndContractBlock();
    CheckInvalidPathChars(path1);
    CheckInvalidPathChars(path2);

    return CombineNoChecks(path1, path2);
}

internal static string CombineNoChecks(string path1, string path2)
{
    if (path2.Length == 0)
        return path1;

    if (path1.Length == 0)
        return path2;

    if (IsPathRooted(path2))
        return path2;

    char ch = path1[path1.Length - 1];
    if (ch != DirectorySeparatorChar && ch != AltDirectorySeparatorChar &&
            ch != VolumeSeparatorChar) 
        return path1 + DirectorySeparatorCharAsString + path2;
    return path1 + path2;
}

Non so quale sia la logica.Immagino che la soluzione sia rimuovere (o tagliare) DirectorySeparatorChar dall'inizio del secondo percorso;magari scrivi il tuo metodo Combine che lo fa e poi chiama Path.Combine().

Altri suggerimenti

Questo è il codice disassemblato da Riflettore .NET per il metodo Path.Combine.Controlla la funzione IsPathRooted.Se il secondo percorso è root (inizia con DirectorySeparatorChar), restituisce il secondo percorso così com'è.

public static string Combine(string path1, string path2)
{
    if ((path1 == null) || (path2 == null))
    {
        throw new ArgumentNullException((path1 == null) ? "path1" : "path2");
    }
    CheckInvalidPathChars(path1);
    CheckInvalidPathChars(path2);
    if (path2.Length == 0)
    {
        return path1;
    }
    if (path1.Length == 0)
    {
        return path2;
    }
    if (IsPathRooted(path2))
    {
        return path2;
    }
    char ch = path1[path1.Length - 1];
    if (((ch != DirectorySeparatorChar) &&
         (ch != AltDirectorySeparatorChar)) &&
         (ch != VolumeSeparatorChar))
    {
        return (path1 + DirectorySeparatorChar + path2);
    }
    return (path1 + path2);
}


public static bool IsPathRooted(string path)
{
    if (path != null)
    {
        CheckInvalidPathChars(path);
        int length = path.Length;
        if (
              (
                  (length >= 1) &&
                  (
                      (path[0] == DirectorySeparatorChar) ||
                      (path[0] == AltDirectorySeparatorChar)
                  )
              )

              ||

              ((length >= 2) &&
              (path[1] == VolumeSeparatorChar))
           )
        {
            return true;
        }
    }
    return false;
}

Volevo risolvere questo problema:

string sample1 = "configuration/config.xml";
string sample2 = "/configuration/config.xml";
string sample3 = "\\configuration/config.xml";

string dir1 = "c:\\temp";
string dir2 = "c:\\temp\\";
string dir3 = "c:\\temp/";

string path1 = PathCombine(dir1, sample1);
string path2 = PathCombine(dir1, sample2);
string path3 = PathCombine(dir1, sample3);

string path4 = PathCombine(dir2, sample1);
string path5 = PathCombine(dir2, sample2);
string path6 = PathCombine(dir2, sample3);

string path7 = PathCombine(dir3, sample1);
string path8 = PathCombine(dir3, sample2);
string path9 = PathCombine(dir3, sample3);

Naturalmente, tutti i percorsi 1-9 dovrebbero contenere alla fine una stringa equivalente.Ecco il metodo PathCombine che ho ideato:

private string PathCombine(string path1, string path2)
{
    if (Path.IsPathRooted(path2))
    {
        path2 = path2.TrimStart(Path.DirectorySeparatorChar);
        path2 = path2.TrimStart(Path.AltDirectorySeparatorChar);
    }

    return Path.Combine(path1, path2);
}

Penso anche che sia abbastanza fastidioso che questa gestione delle stringhe debba essere eseguita manualmente e sarei interessato al motivo di ciò.

Secondo me questo è un bug.Il problema è che esistono due diversi tipi di percorsi “assoluti”.Il percorso "d:\mydir\myfile.txt" è assoluto, anche il percorso "\mydir\myfile.txt" è considerato "assoluto" anche se manca la lettera dell'unità.Il comportamento corretto, a mio avviso, sarebbe quello di anteporre la lettera di unità dal primo percorso quando il secondo percorso inizia con il separatore di directory (e non è un percorso UNC).Consiglierei di scrivere la tua funzione wrapper helper che abbia il comportamento che desideri se ne hai bisogno.

Da MSDN:

Se uno dei percorsi specificati è una stringa di lunghezza zero, questo metodo restituisce l'altro percorso.Se path2 contiene un percorso assoluto, questo metodo restituisce path2.

Nel tuo esempio, path2 è assoluto.

Seguente Cristiano Graus' consigli nel suo blog "Cose che odio di Microsoft" intitolato "Path.Combine è essenzialmente inutile.", ecco la mia soluzione:

public static class Pathy
{
    public static string Combine(string path1, string path2)
    {
        if (path1 == null) return path2
        else if (path2 == null) return path1
        else return path1.Trim().TrimEnd(System.IO.Path.DirectorySeparatorChar)
           + System.IO.Path.DirectorySeparatorChar
           + path2.Trim().TrimStart(System.IO.Path.DirectorySeparatorChar);
    }

    public static string Combine(string path1, string path2, string path3)
    {
        return Combine(Combine(path1, path2), path3);
    }
}

Alcuni consigliano che gli spazi dei nomi dovrebbero entrare in collisione, ...sono andato con Pathy, in modo lieve e per evitare la collisione dello spazio dei nomi con System.IO.Path.

Modificare:Aggiunti controlli dei parametri nulli

Non conoscendo i dettagli effettivi, suppongo che tenti di unirsi come se si collegassero URI relativi.Per esempio:

urljoin('/some/abs/path', '../other') = '/some/abs/other'

Ciò significa che quando unisci un percorso con una barra precedente, stai effettivamente unendo una base a un'altra, nel qual caso la seconda ha la precedenza.

Questo codice dovrebbe fare al caso tuo:

        string strFinalPath = string.Empty;
        string normalizedFirstPath = Path1.TrimEnd(new char[] { '\\' });
        string normalizedSecondPath = Path2.TrimStart(new char[] { '\\' });
        strFinalPath =  Path.Combine(normalizedFirstPath, normalizedSecondPath);
        return strFinalPath;

Motivo:

Il tuo secondo URL è considerato un percorso assoluto, The Combine Il metodo restituirà l'ultimo percorso solo se l'ultimo percorso è un percorso assoluto.

Soluzione: Basta rimuovere la barra iniziale / del tuo secondo Cammino (/SecondPath A SecondPath).Allora funziona come previsto.

Questo in realtà ha senso, in qualche modo, considerando come vengono solitamente trattati i percorsi (relativi):

string GetFullPath(string path)
{
     string baseDir = @"C:\Users\Foo.Bar";
     return Path.Combine(baseDir, path);
}

// Get full path for RELATIVE file path
GetFullPath("file.txt"); // = C:\Users\Foo.Bar\file.txt

// Get full path for ROOTED file path
GetFullPath(@"C:\Temp\file.txt"); // = C:\Temp\file.txt

La vera domanda è:Perché i percorsi, che iniziano con "\", considerato "radicato"?Anche questa era nuova per me, ma funziona così su Windows:

new FileInfo("\windows"); // FullName = C:\Windows, Exists = True
new FileInfo("windows"); // FullName = C:\Users\Foo.Bar\Windows, Exists = False

Questi due metodi dovrebbero evitarti di unire accidentalmente due stringhe che contengono entrambe il delimitatore.

    public static string Combine(string x, string y, char delimiter) {
        return $"{ x.TrimEnd(delimiter) }{ delimiter }{ y.TrimStart(delimiter) }";
    }

    public static string Combine(string[] xs, char delimiter) {
        if (xs.Length < 1) return string.Empty;
        if (xs.Length == 1) return xs[0];
        var x = Combine(xs[0], xs[1], delimiter);
        if (xs.Length == 2) return x;
        var ys = new List<string>();
        ys.Add(x);
        ys.AddRange(xs.Skip(2).ToList());
        return Combine(ys.ToArray(), delimiter);
    }

Questo \ significa "la directory principale dell'unità corrente".Nel tuo esempio significa la cartella "test" nella directory principale dell'unità corrente.Quindi, questo può essere uguale a "c: est".

Se vuoi combinare entrambi i percorsi senza perdere alcun percorso puoi usare questo:

?Path.Combine(@"C:\test", @"\test".Substring(0, 1) == @"\" ? @"\test".Substring(1, @"\test".Length - 1) : @"\test");

Oppure con variabili:

string Path1 = @"C:\Test";
string Path2 = @"\test";
string FullPath = Path.Combine(Path1, Path2.Substring(0, 1) == @"\" ? Path2.Substring(1, Path2.Length - 1) : Path2);

Entrambi i casi restituiscono "C: est est".

Per prima cosa valuto se Path2 inizia con / e se è vero, restituisco Path2 senza il primo carattere.Altrimenti, restituisci l'intero Path2.

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