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

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

  •  09-06-2019
  •  | 
  •  

Frage

Von dem Sofortiges Fenster in Visual Studio:

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

Es scheint, dass sie beide gleich sein sollten.

Das alte FileSystemObject.BuildPath() hat auf diese Weise nicht funktioniert ...

War es hilfreich?

Lösung

Dies ist eine Art philosophische Frage (die vielleicht nur Microsoft wirklich beantworten kann), da es 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 Combine-Methode aus der .NET-Quelle.Man sieht, dass es ruft CombineNoChecks, der dann anruft IsPathRooted auf Pfad2 und gibt diesen Pfad zurück, wenn ja:

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 die Begründung ist.Ich denke, die Lösung besteht darin, DirectorySeparatorChar vom Anfang des zweiten Pfads zu entfernen (oder zu kürzen).Schreiben Sie vielleicht Ihre eigene Combine-Methode, die das erledigt und dann Path.Combine() aufruft.

Andere Tipps

Dies ist der zerlegte Code von .NET-Reflektor für die Path.Combine-Methode.Überprüfen Sie die IsPathRooted-Funktion.Wenn der zweite Pfad ein Root-Pfad ist (mit einem DirectorySeparatorChar beginnt), wird der zweite Pfad unverändert zurückgegeben.

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

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 entsprechende 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 diese String-Verarbeitung manuell durchgeführt werden muss, und mich würde der Grund dafür interessieren.

Meiner Meinung nach ist das ein Bug.Das Problem besteht darin, dass es zwei verschiedene Arten von „absoluten“ Pfaden gibt.Der Pfad „d:\mydir\myfile.txt“ ist absolut, der Pfad „\mydir\myfile.txt“ gilt ebenfalls als „absolut“, obwohl ihm der Laufwerksbuchstabe fehlt.Das richtige Verhalten wäre meiner Meinung nach, den Laufwerksbuchstaben vom ersten Pfad voranzustellen, wenn der zweite Pfad mit dem Verzeichnistrennzeichen beginnt (und kein UNC-Pfad ist).Ich würde empfehlen, bei Bedarf eine eigene Hilfs-Wrapper-Funktion zu schreiben, die das gewünschte Verhalten aufweist.

Aus MSDN:

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

In Ihrem Beispiel ist path2 absolut.

Nachfolgend Christian Graus' Ratschläge in seinem Blog „Things I Hate about Microsoft“ mit dem Titel „Path.Combine ist im Wesentlichen nutzlos.", hier ist 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 dazu, dass die Namensräume kollidieren sollten, ...ich ging mit Pathy, als geringfügig, und um Namespace-Kollision mit zu vermeiden System.IO.Path.

Bearbeiten:Nullparameterprüfungen hinzugefügt

Da ich die tatsächlichen Details nicht kenne, gehe ich davon aus, dass es sich um einen Verbindungsversuch handelt, so wie Sie es mit relativen URIs tun würden.Zum Beispiel:

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

Das heißt, wenn Sie einen Pfad mit einem vorangehenden Schrägstrich verbinden, verbinden Sie tatsächlich eine Basis mit einer anderen, wobei in diesem Fall die zweite Basis Vorrang hat.

Dieser Code sollte den Zweck erfüllen:

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

Grund:

Ihre zweite URL gilt als absoluter Pfad Combine Die Methode gibt den letzten Pfad nur zurück, wenn der letzte Pfad ein absoluter Pfad ist.

Lösung: Entfernen Sie einfach den ersten Schrägstrich / deines zweiten Pfades (/SecondPath Zu SecondPath).Dann funktioniert es so, wie Sie es erwartet haben.

Dies macht in gewisser Weise tatsächlich Sinn, 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 sind Pfade, die mit beginnen? "\", als „verwurzelt“ angesehen?Das war mir auch 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

Diese beiden Methoden sollten Sie davor bewahren, versehentlich zwei Zeichenfolgen zu verbinden, die 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);
    }

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: est“ sein.

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.Substring(0, 1) == @"\" ? Path2.Substring(1, Path2.Length - 1) : Path2);

In beiden Fällen wird „C: est est“ zurückgegeben.

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

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top