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

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

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

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

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

@ Джо, глупый прав! Кроме того, я должен отметить, что эквивалентная функция отлично работает в Node.JS ... Качая головой в Microsoft ...

NH. 06.09.2017 21:19

@zwcloud Для .NET Core / Standard Path.Combine() в основном предназначен для обратной совместимости (с существующим поведением). Вам лучше использовать Path.Join(): «В отличие от метода Combine, метод Join не пытается укоренить возвращаемый путь. (То есть, если path2 является абсолютным путем, метод Join не отбрасывает path1 и не возвращает path2, как это делает метод Combine.)»

Stajs 17.09.2019 05:34
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
200
3
104 328
16
Перейти к ответу Данный вопрос помечен как решенный

Ответы 16

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

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

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

Я думаю, что косые черты следует пояснить. Кроме того, какое отношение это имеет к .NET?

Peter Mortensen 15.10.2018 20:08

От MSDN:

If one of the specified paths is a zero-length string, this method returns the other path. If path2 contains an absolute path, this method returns path2.

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

Ответ принят как подходящий

Это своего рода философский вопрос (на который, возможно, только 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 ().

Глядя на дизассемблированный код (проверьте мой пост), вы в чем-то правы.

Gulzar Nazim 10.09.2008 04:25

Я предполагаю, что это работает таким образом, чтобы обеспечить легкий доступ к алгоритму «текущего рабочего каталога».

BCS 30.04.2009 03:20

Это похоже на выполнение последовательности cd (component) из командной строки. Для меня это звучит разумно.

Adrian Ratnapala 10.02.2014 15:51

Я использую эту обрезку, чтобы получить желаемую строку эффекта strFilePath = Path.Combine (basePath, otherPath.TrimStart (new char [] {'\\', '/'}));

Matthew Lock 23.09.2014 10:44

Я изменил свой рабочий код на Path.Combine на всякий случай, но потом он сломался .. Это так глупо :)

sotn 17.07.2017 17:00

Это дизассемблированный код из .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;
}

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

Он соответствует спецификации, но это не то, чего я ожидал.

dthrasher 26.09.2009 22:11

@Jake Это не позволяет избежать исправления; это несколько людей, думая долго и упорно о том, как сделать что-то, а затем придерживаться, что они согласны. Также обратите внимание на разницу между .Net framework (библиотека, содержащая Path.Combine) и языком C#.

Grault 02.09.2016 07:23

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

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

?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.

Вероятно, безопаснее заменить проверку == @"\" вызовом Path.IsRooted(), поскольку "\" - не единственный персонаж, который нужно учитывать.

rumblefx0 15.11.2016 12:25

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

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

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

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

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

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

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

Следуя совету Кристиан Граус в его блоге «Что я ненавижу в Microsoft» под названием «Path.Combine по сути бесполезен.», вот мое решение:

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.

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

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

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

Причина:

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

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

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

Вопрос не в этом.

LarsTech 13.09.2019 00:58

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

public class MyPath    
{
    public static string ForceCombine(params string[] paths)
    {
        return paths.Aggregate((x, y) => Path.Combine(x, y.TrimStart('\')));
    }
}

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

От времен 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

Другие вопросы по теме