¿Por qué Path.Combine no concatena correctamente los nombres de archivos que comienzan con Path.DirectorySeparatorChar?

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

  •  09-06-2019
  •  | 
  •  

Pregunta

Desde el Ventana inmediata en Visual Estudio:

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

Parece que ambos deberían ser iguales.

El antiguo FileSystemObject.BuildPath() no funcionaba de esta manera...

¿Fue útil?

Solución

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

System.IO.Path.Combinar

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

Aquí está el método Combinar real de la fuente .NET.Puedes ver que llama. CombinarSinCheques, que luego llama IsPathRooted en ruta2 y devuelve esa ruta si es así:

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

No sé cuál es el motivo.Supongo que la solución es quitar (o recortar) DirectorySeparatorChar del principio de la segunda ruta;tal vez escriba su propio método Combine que haga eso y luego llame a Path.Combine().

Otros consejos

Este es el código desensamblado de Reflector .NET para el método Path.Combine.Verifique la función IsPathRooted.Si la segunda ruta tiene raíz (comienza con DirectorySeparatorChar), devuelva la segunda ruta tal 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;
}

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 rutas 1 a 9 deben contener una cadena equivalente al final.Aquí está el método PathCombine que se 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 deba realizarse manualmente, y me interesaría saber el motivo detrás de esto.

En mi opinión, esto es un error.El problema es que existen dos tipos diferentes de caminos "absolutos".La ruta "d:\mydir\myfile.txt" es absoluta, la ruta "\mydir\myfile.txt" también se considera "absoluta" aunque le 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 comienza con el separador de directorio (y no es una ruta UNC).Recomendaría escribir su propia función contenedora de ayuda que tenga el comportamiento que desea si lo necesita.

De MSDN:

Si una de las rutas especificadas es una cadena de longitud cero, este método devuelve la otra ruta.Si ruta2 contiene una ruta absoluta, este método devuelve ruta2.

En su ejemplo, la ruta2 es absoluta.

Siguiente cristian graus' consejo en su blog "Cosas que odio de Microsoft" titulado "Path.Combine es esencialmente inútil.", 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 deberían colisionar,...Yo fui con Pathy, como un desaire, y para evitar la colisión del espacio de nombres con System.IO.Path.

Editar:Se agregaron comprobaciones de parámetros nulos.

Sin conocer los detalles reales, supongo que intenta unirse como si se uniera a URI relativos.Por ejemplo:

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

Esto significa que cuando unes un camino con una barra diagonal precedente, en realidad estás uniendo una base con otra, en cuyo caso la segunda tiene prioridad.

Este código debería funcionar:

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

Razón:

Su segunda URL se considera una ruta absoluta, la Combine El método solo devolverá la última ruta si la última ruta es una ruta absoluta.

Solución: Simplemente elimine la barra inicial / de tu segundo Camino (/SecondPath a SecondPath).Entonces funciona como lo esperabas.

En realidad, esto tiene sentido, de alguna manera, considerando cómo se tratan habitualmente las rutas (relativas):

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é los caminos que comienzan con "\", ¿Considerado "arraigado"?Esto también era nuevo para mí, pero funciona de esa manera en Windows:

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

Estos dos métodos deberían evitarle unir accidentalmente dos cadenas que tienen el delimitador.

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

Este \ 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.Entonces, esto puede ser igual a "c: est".

Si desea combinar ambas rutas sin perder ninguna, puede usar 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: est est".

Primero, evalúo si Path2 comienza con / y si es verdadero, devuelvo Path2 sin el primer carácter.De lo contrario, devuelva la Ruta2 completa.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top