Почему Path.Combine неправильно объединяет имена файлов, начинающиеся с Path.DirectorySeparatorChar?

Из Немедленное окно в Visual Studio:

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

Кажется, что они оба должны быть одинаковыми.

Старый FileSystemObject.BuildPath () так не работал ...


person Kris Erickson    schedule 09.09.2008    source источник
comment
@ Джо, глупый прав! Также я должен отметить, что эквивалентная функция отлично работает в Node.JS. .. Качаю головой на Microsoft ...   -  person NH.    schedule 06.09.2017
comment
@zwcloud Для .NET Core / Standard Path.Combine() в основном предназначен для обратной совместимости (с существующим поведением). Лучше использовать Path.Join(). : В отличие от метода Combine, метод Join не пытается укоренить возвращенный путь. (То есть, если path2 является абсолютным путем, метод Join не отбрасывает path1 и не возвращает path2, как это делает метод Combine.)   -  person Stajs    schedule 17.09.2019


Ответы (16)


Это своего рода философский вопрос (на который, возможно, действительно может ответить только Microsoft), поскольку он делает именно то, что говорится в документации.

System.IO.Path.Combine

«Если путь2 содержит абсолютный путь, этот метод возвращает путь2».

Вот реальный метод Combine из источника .NET. Вы можете видеть, что он вызывает CombineNoChecks, который затем вызывает IsPathRooted на пути 2 и в таком случае возвращает этот путь:

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

Я не знаю, в чем причина. Я предполагаю, что решение состоит в том, чтобы убрать (или обрезать) DirectorySeparatorChar с начала второго пути; возможно, напишите свой собственный метод Combine, который сделает это, а затем вызовет Path.Combine ().

person Ryan Lundy    schedule 09.09.2008
comment
Глядя на дизассемблированный код (проверьте мой пост), вы в чем-то правы. - person Gulzar Nazim; 10.09.2008
comment
Я предполагаю, что это работает таким образом, чтобы обеспечить легкий доступ к текущему алгоритму рабочего каталога. - person BCS; 30.04.2009
comment
Это похоже на выполнение последовательности cd (component) из командной строки. Для меня это звучит разумно. - person Adrian Ratnapala; 10.02.2014
comment
Я использую эту обрезку, чтобы получить желаемую строку эффекта strFilePath = Path.Combine (basePath, otherPath.TrimStart (new char [] {'\\', '/'})); - person Matthew Lock; 23.09.2014
comment
comment
Я изменил свой рабочий код на Path.Combine на всякий случай, но потом он сломался .. Это так глупо :) - person sotn; 17.07.2017

Я хотел решить эту проблему:

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

Конечно, все пути 1–9 должны содержать в конце эквивалентную строку. Вот метод PathCombine, который я придумал:

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

Я также думаю, что это довольно раздражает, что эту обработку строки приходится выполнять вручную, и меня бы интересовала причина этого.

person anhoppe    schedule 30.06.2015

Это дизассемблированный код из .NET Reflector для метода Path.Combine. Проверьте функцию IsPathRooted. Если второй путь является корневым (начинается с DirectorySeparatorChar), вернуть второй путь как есть.

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;
}
person Gulzar Nazim    schedule 09.09.2008

На мой взгляд это ошибка. Проблема в том, что существует два разных типа «абсолютных» путей. Путь «d: \ mydir \ myfile.txt» является абсолютным, путь «\ mydir \ myfile.txt» также считается «абсолютным», даже если в нем отсутствует буква диска. На мой взгляд, правильным поведением будет добавление буквы диска к первому пути, когда второй путь начинается с разделителя каталогов (и не является UNC-путем). Я бы порекомендовал написать свою собственную вспомогательную функцию-оболочку, которая будет иметь желаемое поведение, если вам это нужно.

person Wedge    schedule 10.09.2008
comment
Он соответствует спецификации, но это не то, чего я ожидал. - person dthrasher; 26.09.2009
comment
@Jake Это не позволяет избежать исправления; это несколько человек, которые долго и упорно думают о том, как что-то сделать, а затем придерживаются того, с чем они согласны. Также обратите внимание на разницу между .Net framework (библиотека, содержащая Path.Combine) и языком C #. - person Grault; 02.09.2016

Из MSDN:

Если один из указанных путей является строкой нулевой длины, этот метод возвращает другой путь. Если путь2 содержит абсолютный путь, этот метод возвращает путь2.

В вашем примере path2 является абсолютным.

person nickd    schedule 09.09.2008

Следуя совету Кристиана Грауса в его блоге "Что я ненавижу в Microsoft" под названием «Путь. Комбинировать практически бесполезно. ", вот мое решение:

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

Некоторые советуют, что пространства имен должны конфликтовать, ... Я пошел с Pathy, как небольшой, и чтобы избежать столкновения пространств имен с System.IO.Path.

Изменить: добавлены проверки нулевого параметра.

person ergohack    schedule 19.04.2017

Этот код должен помочь:

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

Не зная фактических деталей, я предполагаю, что он пытается присоединиться, как если бы вы могли присоединиться к относительным URI. Например:

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

Это означает, что когда вы присоединяетесь к пути с предыдущей косой чертой, вы фактически присоединяете одну базу к другой, и в этом случае вторая получает приоритет.

person elarson    schedule 09.09.2008
comment
Я думаю, что косые черты следует пояснить. Кроме того, какое отношение это имеет к .NET? - person Peter Mortensen; 15.10.2018

Причина:

Ваш второй URL-адрес считается абсолютным путем. Метод Combine вернет только последний путь, если последний путь является абсолютным путем.

Решение: просто удалите начальную косую черту / второго пути (от /SecondPath до SecondPath). Тогда это работает так, как вы исключили.

person Arad    schedule 14.05.2018

Это действительно имеет смысл, учитывая, как обычно обрабатываются (относительные) пути:

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

Настоящий вопрос: почему пути, начинающиеся с "\", считаются «корневыми»? Для меня это тоже было ново, но так работает на Windows:

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

Я использовал агрегатную функцию, чтобы объединить пути, как показано ниже:

public class MyPath    
{
    public static string ForceCombine(params string[] paths)
    {
        return paths.Aggregate((x, y) => Path.Combine(x, y.TrimStart('\\')));
    }
}
person LazZiya    schedule 20.09.2019
comment
Этот работает, так как его можно вставить везде, где есть проблема. На заметку: какая досадная проблема! - person Jamie Nicholl-Shelley; 26.05.2021

Если вы хотите объединить оба пути без потери пути, вы можете использовать это:

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

Или с переменными:

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

В обоих случаях возвращается «C: \ test \ test».

Сначала я оцениваю, начинается ли Path2 с /, и если это правда, возвращаю Path2 без первого символа. В противном случае верните полный путь 2.

person Ferri    schedule 18.07.2014
comment
Вероятно, безопаснее заменить проверку == @"\" вызовом Path.IsRooted(), поскольку "\" - не единственный персонаж, который нужно учитывать. - person rumblefx0; 15.11.2016
comment
Вместо этого вы можете использовать .Trim ('\') - person Darina Sergeivna; 09.04.2021

Удалите начальную косую черту ('\') во втором параметре (path2) Path.Combine.

person shanmuga raja    schedule 12.09.2019
comment
Вопрос не в этом. - person LarsTech; 13.09.2019

Эти два метода должны уберечь вас от случайного соединения двух строк, в каждой из которых есть разделитель.

    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);
    }
person Don Rolling    schedule 01.08.2017

Это \ означает "корневой каталог текущего диска". В вашем примере это означает «тестовую» папку в корневом каталоге текущего диска. Таким образом, это может быть равно «c: \ test».

person Estevez    schedule 23.04.2014

Как упоминал Райан, он делает именно то, что говорится в документации.

Из времен DOS различаются текущий диск и текущий путь. \ - это корневой путь, но для ТЕКУЩЕГО ДИСКА.

Для каждого «диска» есть отдельный «текущий путь». Если вы меняете диск с помощью cd D:, вы меняете не текущий путь на D:\, а на: «D: \ something \ was \ the \ last \ path \ accessed \ on \ this \ disk» ...

Итак, в Windows буквальный @"\x" означает: «ТЕКУЩИЙ ДИСК: \ x». Следовательно, Path.Combine(@"C:\x", @"\y") имеет в качестве второго параметра корневой путь, а не относительный, хотя и не на известном диске ... И поскольку неизвестно, какой может быть «текущий диск», python возвращает "\\y".

>cd C:
>cd \mydironC\apath
>cd D:
>cd \mydironD\bpath
>cd C:
>cd
>C:\mydironC\apath
person ilias iliadis    schedule 17.12.2019