2008-09-09 5 views
140

De la fenêtre immédiate dans Visual Studio:Pourquoi Path.Combine ne concatène pas correctement les noms de fichiers commençant par Path.DirectorySeparatorChar?

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

Il semble qu'ils devraient tous deux être le même.

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

+40

OMG c'est tellement stupide qu'il "fonctionne" de cette façon. – Joe

+0

[Il ne change toujours pas dans le noyau .NET.] (Https://github.com/dotnet/coreclr/blob/fb86c0294a999b2c7bd1e13da1fdc0d3c2f701e5/src/mscorlib/shared/System/IO/Path.cs#L189) – zwcloud

+0

@Joe , stupide a raison! En outre, je dois souligner que [la fonction équivalente] (https://nodejs.org/api/path.html#path_path_join_paths) fonctionne très bien dans Node.JS ... Secouant la tête chez Microsoft ... –

Répondre

158

Ceci est une sorte de question philosophique (que peut-être seul Microsoft peut vraiment répondre), car il fait exactement ce que dit la documentation.

System.IO.Path.Combine

"Si path2 contient un chemin absolu, cette méthode renvoie path2."

Here's the actual Combine method à partir de la source .NET. Vous pouvez voir qu'il appelle CombineNoChecks, qui appelle ensuite IsPathRooted sur path2 et renvoie ce chemin si c'est le cas.

Je ne sais pas quelle est la justification. Je suppose que la solution est de dépouiller (ou Trim) DirectorySeparatorChar du début du deuxième chemin; peut-être écrire votre propre méthode de Combine qui fait cela, puis appelle Path.Combine().

+0

le code démonté (vérifiez mon post), vous avez raison d'une certaine manière. –

+6

Je suppose que cela fonctionne de cette façon pour permettre un accès facile à l'algorithme "courant de travail". – BCS

+0

Cela semble fonctionner comme une séquence de 'cd (component)' depuis la ligne de commande. Cela me semble raisonnable. –

5

Ne connaissant pas les détails réels, je suppose que cela fait une tentative de se joindre comme vous pouvez rejoindre les URIs relatives. Par exemple:

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

Cela signifie que lorsque vous rejoignez un chemin avec une barre oblique précédente, vous joindrez en fait une base à l'autre, dans ce cas, la seconde est prioritaire.

6

De MSDN:

Si l'un des chemins spécifiés est une chaîne de longueur nulle, cette méthode retourne l'autre chemin. Si path2 contient un chemin absolu, cette méthode renvoie path2.

Dans votre exemple, path2 est absolu.

22

Il s'agit du code désassemblé de .NET Reflector pour la méthode Path.Combine. Vérifiez la fonction IsPathRooted. Si le second chemin est rooté (commence par DirectorySeparatorChar), retourne le second 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; 
} 
14

À mon avis, c'est un bug. Le problème est qu'il existe deux types différents de chemins "absolus". Le chemin "d: \ mydir \ myfile.txt" est absolu, le chemin "\ mydir \ myfile.txt" est également considéré comme "absolu" même s'il manque la lettre de lecteur. Le comportement correct, à mon avis, serait de préfixer la lettre de lecteur à partir du premier chemin lorsque le second chemin commence avec le séparateur de répertoire (et n'est pas un chemin UNC). Je recommanderais d'écrire votre propre fonction de wrapper qui a le comportement que vous désirez si vous en avez besoin.

+1

Il correspond à la spécification, mais ce n'est pas ce que je m'attendais non plus. – dthrasher

+0

@Jake Cela n'évite pas une correction de bogue; c'est plusieurs personnes qui réfléchissent longuement et durement sur la façon de faire quelque chose, et qui s'en tiennent à tout ce sur quoi ils sont d'accord. Notez également la différence entre le framework .Net (une bibliothèque qui contient 'Path.Combine') et le langage C#. – Grault

0

Ceci \ signifie "le répertoire racine du lecteur actuel". Dans votre exemple, cela signifie le dossier "test" dans le répertoire racine du lecteur actuel.Donc, cela peut être égal à "c: \ test"

0

Si vous voulez combiner les deux chemins sans perdre le chemin que vous pouvez utiliser ceci:

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

Ou avec des 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 renvoient "C: \ test \ test". D'abord, j'évalue si Path2 commence par/et si c'est vrai, retourne Path2 sans le premier caractère. Sinon, renvoyez le chemin complet Path2.

+0

Il est probablement plus sûr de remplacer la vérification '== @" \ "' par un appel 'Path.IsRooted()' puisque '" \ "' n'est pas le seul caractère à prendre en compte. – rumblefx0

3

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

Ok, déjà une longue liste de réponses, voici le mien ;-)

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 doivent contenir une chaîne équivalente à la fin. Voici la méthode PathCombine 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 qu'il est tout à fait gênant que cette chaîne de traitement doit être fait manuellement, je serais intéressé par la raison derrière cela.

0

Cela fait réellement sens, d'une certaine façon, compte tenu de la façon dont (relative) des chemins sont traités habituellement:

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 les chemins qui commencent par "\" comme considéré « Enracinés ». C'était nouveau pour moi, mais it works that way on windows:

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

Après Christian Graus 'conseils dans ses "choses que je déteste à propos de Microsoft" blog intitulé "Path.Combine is essentially useless.", 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 devraient entrer en collision, ... Je suis allé avec Pathy, comme une légère, et pour éviter la collision d'espace de noms avec System.IO.Path.

Modifier: contrôles de paramètres null Ajouté

0

Ces deux méthodes devraient vous faire économiser de se joindre accidentellement deux chaînes qui ont tous deux 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); 
    } 
Questions connexes