Warum verkettet Path.Combine Dateinamen, die mit Path.DirectorySeparatorChar beginnen, nicht richtig?

181

Aus dem Direktfenster in Visual Studio:

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

Es scheint, dass beide gleich sein sollten.

Das alte FileSystemObject.BuildPath () hat so nicht funktioniert ...

Kris Erickson
quelle
@ Joe, dumm ist richtig! Außerdem muss ich darauf hinweisen, dass die entsprechende Funktion in Node.JS einwandfrei funktioniert ... Kopfschütteln bei Microsoft ...
NH.
2
@zwcloud Für .NET Core / Standard Path.Combine()dient hauptsächlich die Abwärtskompatibilität (mit dem vorhandenen Verhalten). Verwenden Sie besser Path.Join(): "Im Gegensatz zur Combine-Methode versucht die Join-Methode nicht, den zurückgegebenen Pfad zu rooten. (Wenn also path2 ein absoluter Pfad ist, verwirft die Join-Methode Pfad1 nicht und gibt path2 nicht als Combine zurück Methode tut.) "
Stajs

Antworten:

201

Dies ist eine Art philosophische Frage (die vielleicht nur Microsoft wirklich beantworten kann), da sie genau das tut, was in der Dokumentation steht.

System.IO.Path.Combine

"Wenn Pfad2 einen absoluten Pfad enthält, gibt diese Methode Pfad2 zurück."

Hier ist die eigentliche Kombinationsmethode aus der .NET-Quelle. Sie können sehen, dass CombineNoChecks aufgerufen wird , das dann IsPathRooted auf Pfad2 aufruft und diesen Pfad zurückgibt, wenn dies der Fall ist:

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

Ich weiß nicht, was das Grundprinzip ist. Ich denke, die Lösung besteht darin, DirectorySeparatorChar vom Anfang des zweiten Pfades zu entfernen (oder zu trimmen). Schreiben Sie möglicherweise Ihre eigene Combine-Methode, die dies ausführt, und rufen Sie dann Path.Combine () auf.

Ryan Lundy
quelle
Wenn Sie sich den zerlegten Code ansehen (siehe meinen Beitrag), haben Sie in gewisser Weise Recht.
Gulzar Nazim
7
Ich würde vermuten, dass es so funktioniert, um einen einfachen Zugriff auf den Algorithmus "Aktuelles Arbeitsverzeichnis" zu ermöglichen.
BCS
Es scheint so zu funktionieren, als würde man eine Sequenz cd (component)von der Kommandozeile aus ausführen. Klingt für mich vernünftig.
Adrian Ratnapala
11
Ich benutze diese Trimmung, um die gewünschte Effektzeichenfolge zu erhalten. StrFilePath = Path.Combine (basePath, otherPath.TrimStart (neues Zeichen [] {'\\', '/'}));
Matthew Lock
3
Ich habe meine Arbeits Code ändern in Path.Combinenur um sicher zu sein , aber dann brach es .. Es ist so dumm :)
SOTN
23

Dies ist der disassemblierte Code aus der .NET Reflector for Path.Combine-Methode. Überprüfen Sie die IsPathRooted-Funktion. Wenn der zweite Pfad gerootet ist (beginnt mit einem DirectorySeparatorChar), geben Sie den zweiten Pfad unverändert zurück.

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;
}
Gulzar Nazim
quelle
23

Ich wollte dieses Problem lösen:

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

Natürlich sollten alle Pfade 1-9 am Ende eine äquivalente Zeichenfolge enthalten. Hier ist die PathCombine-Methode, die ich mir ausgedacht habe:

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

Ich finde es auch ziemlich ärgerlich, dass dieses String-Handling manuell durchgeführt werden muss, und ich würde mich für den Grund dafür interessieren.

anhoppe
quelle
19

Meiner Meinung nach ist dies ein Fehler. Das Problem ist, dass es zwei verschiedene Arten von "absoluten" Pfaden gibt. Der Pfad "d: \ mydir \ myfile.txt" ist absolut, der Pfad "\ mydir \ myfile.txt" wird auch als "absolut" betrachtet, obwohl der Laufwerksbuchstabe fehlt. Meiner Meinung nach besteht das richtige Verhalten darin, den Laufwerksbuchstaben aus dem ersten Pfad voranzustellen, wenn der zweite Pfad mit dem Verzeichnistrennzeichen beginnt (und kein UNC-Pfad ist). Ich würde empfehlen, eine eigene Helfer-Wrapper-Funktion zu schreiben, die das gewünschte Verhalten aufweist, wenn Sie es benötigen.

Keil
quelle
7
Es entspricht der Spezifikation, ist aber auch nicht das, was ich erwartet hätte.
Dthrasher
@Jake Damit wird ein Bugfix nicht vermieden. Das sind mehrere Leute, die lange und gründlich darüber nachdenken, wie sie etwas tun sollen, und sich dann an das halten, worüber sie sich einig sind. Beachten Sie auch den Unterschied zwischen dem .Net-Framework (einer Bibliothek, die enthält Path.Combine) und der C # -Sprache.
Grault
9

Von MSDN :

Wenn einer der angegebenen Pfade eine Zeichenfolge mit der Länge Null ist, gibt diese Methode den anderen Pfad zurück. Wenn path2 einen absoluten Pfad enthält, gibt diese Methode path2 zurück.

In Ihrem Beispiel ist path2 absolut.

nickd
quelle
7

Nach dem Rat von Christian Graus in seinem Blog "Dinge, die ich an Microsoft hasse" mit dem Titel " Path.Combine ist im Wesentlichen nutzlos. " Ist hier meine Lösung:

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

Einige raten, dass die Namespaces kollidieren sollten, ... Ich ging mit Pathy, als geringfügig, und um eine Namespace-Kollision mit zu vermeiden System.IO.Path.

Bearbeiten : Nullparameterprüfungen hinzugefügt

Ergohack
quelle
4

Dieser Code sollte den Trick machen:

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

Da ich die tatsächlichen Details nicht kenne, wird davon ausgegangen, dass versucht wird, sich anzuschließen, als ob Sie relativen URIs beitreten könnten. Beispielsweise:

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

Dies bedeutet, dass Sie beim Verbinden eines Pfads mit einem vorhergehenden Schrägstrich tatsächlich eine Basis mit einer anderen verbinden. In diesem Fall hat die zweite Priorität.

Elarson
quelle
Ich denke, die Schrägstriche sollten erklärt werden. Was hat das mit .NET zu tun?
Peter Mortensen
3

Grund:

Ihre zweite URL wird als absoluter Pfad betrachtet. Die CombineMethode gibt den letzten Pfad nur zurück, wenn der letzte Pfad ein absoluter Pfad ist.

Lösung: Entfernen Sie einfach den ersten Schrägstrich /Ihres zweiten Pfads ( /SecondPathnach SecondPath). Dann funktioniert es wie von Ihnen ausgenommen.

Amir Hossein Ahmadi
quelle
3

Dies ist in gewisser Weise sinnvoll, wenn man bedenkt, wie (relative) Pfade normalerweise behandelt werden:

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

Die eigentliche Frage ist: Warum werden Pfade, die mit beginnen "\", als "verwurzelt" betrachtet? Das war auch für mich neu, aber unter Windows funktioniert es so :

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

Wenn Sie beide Pfade kombinieren möchten, ohne einen Pfad zu verlieren, können Sie Folgendes verwenden:

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

Oder mit Variablen:

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

Beide Fälle geben "C: \ test \ test" zurück.

Zuerst bewerte ich, ob Path2 mit / beginnt, und wenn es wahr ist, gebe Path2 ohne das erste Zeichen zurück. Andernfalls geben Sie den vollständigen Pfad2 zurück.

Ferri
quelle
1
Es ist wahrscheinlich sicherer, den == @"\"Scheck durch einen zu ersetzenPath.IsRooted() Anruf , da dies "\"nicht das einzige Zeichen ist, das berücksichtigt werden muss.
rumblefx0
0

Diese beiden Methoden sollten Sie davor bewahren, versehentlich zwei Zeichenfolgen zu verbinden, in denen beide das Trennzeichen enthalten.

    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);
    }
Don Rolling
quelle
0

Dies bedeutet "das Stammverzeichnis des aktuellen Laufwerks". In Ihrem Beispiel bedeutet dies den Ordner "test" im Stammverzeichnis des aktuellen Laufwerks. Dies kann also gleich "c: \ test" sein.

Estevez
quelle
0

Entfernen Sie den Start-Schrägstrich ('\') im zweiten Parameter (Pfad2) von Path.Combine.

Shanmuga Raja
quelle
Die Frage stellt das nicht.
LarsTech
0

Ich habe die Aggregatfunktion verwendet, um die Kombination von Pfaden wie folgt zu erzwingen:

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

Wie von Ryan erwähnt, macht es genau das, was in der Dokumentation steht.

Von den DOS-Zeiten werden die aktuelle Festplatte und der aktuelle Pfad unterschieden. \ist der Root-Pfad, aber für die CURRENT DISK.

Für jede " Festplatte " gibt es einen separaten " aktuellen Pfad ". Wenn Sie die Festplatte mit ändern, ändern cd D:Sie nicht den aktuellen Pfad zuD:\ , sondern in: "D: \ was auch immer \ war \ der \ letzte \ Pfad \, auf den \ auf \ dieser \ Festplatte zugegriffen wurde" ...

In Windows @"\x"bedeutet ein Literal also: "CURRENTDISK: \ x". Daher Path.Combine(@"C:\x", @"\y")hat als zweiter Parameter einen Root-Pfad, keinen Verwandten, obwohl nicht auf einer bekannten Festplatte ... Und da nicht bekannt ist, welche die «aktuelle Festplatte» sein könnte, gibt Python zurück "\\y".

>cd C:
>cd \mydironC\apath
>cd D:
>cd \mydironD\bpath
>cd C:
>cd
>C:\mydironC\apath
ilias iliadis
quelle