2008-09-09 14 views
140

Desde la ventana Inmediato en Visual Studio:¿Por qué Path.Combine no concatena correctamente los nombres de archivo que comienzan con Path.DirectorySeparatorChar?

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

Parece que ambos deberían ser los mismos.

El viejo FileSystemObject.BuildPath() no funciona de esta manera ...

+40

OMG esto es tan estúpido que "funciona" de esta manera. – Joe

+0

[Todavía no cambia en .NET core.] (Https://github.com/dotnet/coreclr/blob/fb86c0294a999b2c7bd1e13da1fdc0d3c2f701e5/src/mscorlib/shared/System/IO/Path.cs#L189) – zwcloud

+0

@Joe , estúpido es correcto! Además, debo señalar que [la función equivalente] (https://nodejs.org/api/path.html#path_path_join_paths) funciona bien en Node.JS ... Sacudiendo la cabeza ante Microsoft ... –

Respuesta

158

Esta es una pregunta filosófica (que quizás solo Microsoft realmente pueda responder), ya que está haciendo exactamente lo que dice la documentación.

System.IO.Path.Combine

"Si ruta2 contiene una ruta absoluta, este método devuelve ruta2."

Here's the actual Combine method de la fuente .NET. Puede ver que llama al CombineNoChecks, que luego llama al IsPathRooted en la ruta 2 y devuelve esa ruta si es así.

No sé cuál es el motivo. Supongo que la solución es quitar (o recortar) DirectorySeparatorChar desde el comienzo de la segunda ruta; quizás escriba su propio método Combine que hace eso y luego llama a Path.Combine().

+0

Mirando el código desensamblado (revisa mi publicación), de alguna manera estás en lo correcto. –

+6

Supongo que funciona de esa manera para permitir un acceso fácil al algoritmo "dir de trabajo actual". – BCS

+0

Parece que funciona como hacer una secuencia de 'cd (componente)' desde la línea de comando. Me parece razonable. –

5

Sin saber los detalles reales, mi suposición es que se hace un intento de unirse de que se puede unirse a URIs relativas. Por ejemplo:

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

Esto significa que cuando se une a un camino con una barra precedente, en realidad se está uniendo a una base a otra, en cuyo caso la segunda se pone precedencia.

6

De MSDN:

Si una de las rutas especificadas es una cadena de longitud cero, este método devuelve el otro camino. Si path2 contiene una ruta absoluta, este método devuelve path2.

En su ejemplo, path2 es absoluta.

22

Este es el código desensamblado de .NET Reflector para el método Path.Combine. Compruebe la función IsPathRooted. Si la segunda ruta está rooteada (comienza con un DirectorySeparatorChar), regrese la segunda ruta como 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

En mi opinión, esto es un error. El problema es que hay dos tipos diferentes de rutas "absolutas". La ruta "d: \ mydir \ myfile.txt" es absoluta, la ruta "\ mydir \ myfile.txt" también se considera "absoluta" aunque falte la letra de la unidad. El comportamiento correcto, en mi opinión, sería anteponer la letra de la unidad de la primera ruta cuando la segunda ruta comience con el separador de directorio (y no sea una ruta UNC). Te recomendaría escribir tu propia función de envoltura auxiliar que tenga el comportamiento que deseas si la necesitas.

+1

Coincide con la especificación, pero tampoco es lo que esperaba. – dthrasher

+0

@Jake Eso no evita una corrección de errores; Son varias personas que piensan mucho sobre cómo hacer algo y luego se aferran a lo que acuerden. Además, tenga en cuenta la diferencia entre el .Net framework (una biblioteca que contiene 'Path.Combine') y el lenguaje C#. – Grault

0

Esto \ significa "el directorio raíz de la unidad actual". En su ejemplo, significa la carpeta "prueba" en el directorio raíz de la unidad actual.Por lo tanto, esto puede ser igual a "c: \ test"

0

Si desea combinar ambos caminos sin perder cualquier camino puede utilizar esto:

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

O con variables:

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

Ambos casos devuelven "C: \ prueba \ prueba".

Primero, evalúo si Path2 comienza con/y si es verdadero, devuelve Path2 sin el primer caracter. De lo contrario, devuelve Path2 completo.

+0

Probablemente sea más seguro reemplazar la comprobación '== @" \ "' por una llamada 'Path.IsRooted()' ya que '" \ "' no es el único carácter que se debe tener en cuenta. – rumblefx0

3

Este código debe hacer el truco:

 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, ya una larga lista de respuestas, aquí es mío ;-)

quería resolver este 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); 

Por supuesto, todas las pathes 1-9 deben contener una cadena equivalente al final. Aquí está el método PathCombine me ocurrió:

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

También creo que es bastante molesto que este manejo de cadenas se tiene que hacer manualmente, estaría interesado en la razón detrás de esto.

0

En realidad, esto tiene sentido, de alguna manera, teniendo en cuenta la (relativa) caminos suelen ser tratados:

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 verdadera pregunta es, ¿por qué caminos que comienzan con "\" como se considera "arraigados". Esto era nuevo para mí, pero 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

Siguiendo Christian Graus 'consejos en sus "cosas que odio de Microsoft" blog titulado "Path.Combine is essentially useless.", aquí está mi solución:

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

Algunos aconsejan que los espacios de nombres deben colisionar, ... Fui con Pathy, como un ligero, y para evitar la colisión del espacio de nombres con System.IO.Path.

Editar: Añadido parámetros cheques nulos

0

Estos dos métodos que deben salvar de unirse accidentalmente dos cadenas que ambos tienen el delimitador en ellos.

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