Pourquoi ne Chemin.Combinez pas correctement concaténer les fichiers qui commencent avec Chemin d'accès.DirectorySeparatorChar?

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

  •  09-06-2019
  •  | 
  •  

Question

À partir de la Fenêtre Exécution dans Visual Studio:

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

Il semble qu'ils doivent être la même.

L'ancien FileSystemObject.BuildPath() ne fonctionne pas de cette façon...

Était-ce utile?

La solution

C'est le genre de question philosophique (qui peut-être que Microsoft peut vraiment répondre), puisque c'est exactement ce que dit la documentation.

Système.IO.Chemin d'accès.Combiner

"Si path2 contient un chemin d'accès absolu, cette méthode retourne path2."

Voici la réelle méthode de combinaison de la .NET de source.Vous pouvez voir qu'il appelle CombineNoChecks, qui appelle ensuite IsPathRooted sur path2 et retourne ce chemin si oui:

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;
}

Je ne sais pas ce que la raison est.Je suppose que la solution est d'enlever (ou ajuster) DirectorySeparatorChar depuis le début de la deuxième chemin d'accès;peut-être écrire votre propre méthode de combinaison qui le fait et appelle ensuite le Chemin d'accès.Combiner().

Autres conseils

C'est le code désassemblé de .NET Réflecteur pour le Chemin.Méthode de combinaison.Vérifier IsPathRooted fonction.Si la seconde voie est enracinée (qui commence par un DirectorySeparatorChar), à la deuxième chemin tel qu'il est.

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;
}

Je voulais résoudre ce problème:

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);

Bien sûr, tous les chemins 1-9 doit contenir une chaîne équivalente à la fin.Voici la PathCombine méthode je suis venu avec:

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);
}

Je pense aussi que c'est assez ennuyeux que cette chaîne de manutention doit être fait manuellement, et je serais intéressé par la raison derrière cela.

À mon avis c'est un bug.Le problème, c'est qu'il y a deux types différents de "absolue" des chemins.Le chemin d'accès "d:\mydir\myfile.txt" est absolue, le chemin "\mydir\myfile.txt" est également considéré comme "absolue", même si il est absent de la lettre de lecteur.Le comportement correct, à mon avis, serait de la faire précéder de la lettre de lecteur de la première chemin d'accès lors de la deuxième chemin commence avec le séparateur de répertoire (et n'est pas un chemin d'accès UNC).Je recommande d'écrire votre propre helper de fonction wrapper qui a le comportement que vous désirez si vous en avez besoin.

À partir de MSDN:

Si l'un des chemins est une chaîne de longueur nulle, cette méthode renvoie le chemin d'autres.Si path2 contient un chemin d'accès absolu, cette méthode retourne path2.

Dans votre exemple, path2 est absolue.

La suite Christian Graus les conseils de ses "Choses que je Déteste à propos de Microsoft" blog intitulé "Chemin d'accès.Moissonneuse-batteuse est essentiellement inutiles.", voici ma solution:

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);
    }
}

Certains conseillent que les espaces de noms entrent en collision, ...Je suis allé avec Pathy, comme un léger, et pour éviter la collision avec un espace de noms System.IO.Path.

Modifier:Ajouté le paramètre null vérifie

Ne connaissant pas les détails, je pense qu'il fait une tentative pour rejoindre, comme vous pourriez vous joindre à Uri relatifs.Par exemple:

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

Cela signifie que lorsque vous rejoignez un chemin avec une barre oblique, vous êtes en fait de rejoindre une base à une autre, auquel cas la seconde devient la priorité.

Ce code devrait faire l'affaire:

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

Raison:

Votre deuxième URL est considérée comme un chemin absolu, L' Combine la méthode ne retourne que le dernier chemin si le dernier chemin est un chemin d'accès absolu.

Solution: Il suffit de retirer le départ slash / de votre deuxième Chemin (/SecondPath pour SecondPath).Alors qu'il fonctionne comme vous l'exception.

Cela fait réellement sens, d'une certaine façon, compte tenu du (relatif), les chemins sont traités généralement:

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 vraie question est:Pourquoi sont les chemins qui commencent avec "\", considérée comme la "racine"?C'était nouveau pour moi aussi, mais il fonctionne de cette manière sur Windows:

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

Ces deux méthodes devraient vous sauver de l'accidentellement joindre deux cordes que les deux ont le délimiteur en eux.

    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);
    }

Cette \ "signifie" le répertoire racine du lecteur en cours".Dans votre exemple, cela signifie que le dossier "test" dans le courant de commande du répertoire racine.Si, cela peut être égal à "c: est".

Si vous souhaitez combiner les deux chemins d'accès sans perdre en chemin, vous pouvez utiliser ceci:

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

Ou avec les variables:

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

Les deux cas de retour "C: est est".

Tout d'abord, je évaluer si Path2 commence avec / et si c'est vrai, le retour Path2 sans le premier caractère.Sinon, le retour de la pleine Path2.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top