From 48d52d8ce63c803112b554dfc449b645eb1e6bad Mon Sep 17 00:00:00 2001 From: Jeremy Kuhne Date: Sun, 25 Feb 2018 11:11:41 -0800 Subject: [PATCH 1/6] Add Path.Join() methods. See #25536. --- src/mscorlib/shared/System/IO/Path.Windows.cs | 6 +- src/mscorlib/shared/System/IO/Path.cs | 157 ++++++++++-------- src/mscorlib/shared/System/IO/PathInternal.cs | 3 +- 3 files changed, 95 insertions(+), 71 deletions(-) diff --git a/src/mscorlib/shared/System/IO/Path.Windows.cs b/src/mscorlib/shared/System/IO/Path.Windows.cs index c92211f73150..5083b5046087 100644 --- a/src/mscorlib/shared/System/IO/Path.Windows.cs +++ b/src/mscorlib/shared/System/IO/Path.Windows.cs @@ -80,7 +80,7 @@ public static string GetFullPath(string path, string basePath) // Path is current drive rooted i.e. starts with \: // "\Foo" and "C:\Bar" => "C:\Foo" // "\Foo" and "\\?\C:\Bar" => "\\?\C:\Foo" - combinedPath = CombineNoChecks(GetPathRoot(basePath), path.AsSpan().Slice(1)); + combinedPath = CombineInternal(GetPathRoot(basePath), path.AsSpan().Slice(1)); } else if (length >= 2 && PathInternal.IsValidDriveChar(path[0]) && path[1] == PathInternal.VolumeSeparatorChar) { @@ -92,7 +92,7 @@ public static string GetFullPath(string path, string basePath) // Matching root // "C:Foo" and "C:\Bar" => "C:\Bar\Foo" // "C:Foo" and "\\?\C:\Bar" => "\\?\C:\Bar\Foo" - combinedPath = CombineNoChecks(basePath, path.AsSpan().Slice(2)); + combinedPath = CombineInternal(basePath, path.AsSpan().Slice(2)); } else { @@ -107,7 +107,7 @@ public static string GetFullPath(string path, string basePath) // "Simple" relative path // "Foo" and "C:\Bar" => "C:\Bar\Foo" // "Foo" and "\\?\C:\Bar" => "\\?\C:\Bar\Foo" - combinedPath = CombineNoChecks(basePath, path); + combinedPath = CombineInternal(basePath, path); } // Device paths are normalized by definition, so passing something of this format diff --git a/src/mscorlib/shared/System/IO/Path.cs b/src/mscorlib/shared/System/IO/Path.cs index 586ddf3a6998..f417b88eec1a 100644 --- a/src/mscorlib/shared/System/IO/Path.cs +++ b/src/mscorlib/shared/System/IO/Path.cs @@ -296,7 +296,7 @@ public static string Combine(string path1, string path2) if (path1 == null || path2 == null) throw new ArgumentNullException((path1 == null) ? nameof(path1) : nameof(path2)); - return CombineNoChecks(path1, path2); + return CombineInternal(path1, path2); } public static string Combine(string path1, string path2, string path3) @@ -304,7 +304,7 @@ public static string Combine(string path1, string path2, string path3) if (path1 == null || path2 == null || path3 == null) throw new ArgumentNullException((path1 == null) ? nameof(path1) : (path2 == null) ? nameof(path2) : nameof(path3)); - return CombineNoChecks(path1, path2, path3); + return CombineInternal(path1, path2, path3); } public static string Combine(string path1, string path2, string path3, string path4) @@ -312,7 +312,7 @@ public static string Combine(string path1, string path2, string path3, string pa if (path1 == null || path2 == null || path3 == null || path4 == null) throw new ArgumentNullException((path1 == null) ? nameof(path1) : (path2 == null) ? nameof(path2) : (path3 == null) ? nameof(path3) : nameof(path4)); - return CombineNoChecks(path1, path2, path3, path4); + return CombineInternal(path1, path2, path3, path4); } public static string Combine(params string[] paths) @@ -383,11 +383,74 @@ public static string Combine(params string[] paths) return StringBuilderCache.GetStringAndRelease(finalPath); } - /// - /// Combines two paths. Does no validation of paths, only concatenates the paths - /// and places a directory separator between them if needed. - /// - private static string CombineNoChecks(ReadOnlySpan first, ReadOnlySpan second) + // Unlike Combine(), Join() methods do not consider rooting. They simply combine paths, ensuring that there + // is a directory separator between them. + + public static string Join(ReadOnlySpan path1, ReadOnlySpan path2) + { + if (path1.Length == 0) + return path2.Length == 0 ? string.Empty : new string(path2); + + return JoinInternal(path1, path2); + } + + public static string Join(ReadOnlySpan path1, ReadOnlySpan path2, ReadOnlySpan path3) + { + if (path1.Length == 0) + return Join(path2, path3); + + if (path2.Length == 0) + return Join(path1, path3); + + if (path3.Length == 0) + return Join(path1, path2); + + return JoinInternal(path1, path2, path3); + } + + public static bool TryJoin(Span destination, out int charsWritten, ReadOnlySpan path1, ReadOnlySpan path2) + { + charsWritten = 0; + if (path1.Length == 0) + { + if (destination.Length < path2.Length) + { + return false; + } + + path2.CopyTo(destination); + charsWritten = path2.Length; + return true; + } + + if (path2.Length == 0) + { + if (destination.Length < path1.Length) + { + return false; + } + + path1.CopyTo(destination); + charsWritten = path1.Length; + return true; + } + + bool needsSeparator = !(PathInternal.EndsInDirectorySeparator(path1) || PathInternal.EndsInDirectorySeparator(path2)); + int charsNeeded = path1.Length + path2.Length + (needsSeparator ? 1 : 0); + if (destination.Length < charsNeeded) + return false; + + path1.CopyTo(destination); + if (needsSeparator) + destination[path1.Length] = DirectorySeparatorChar; + + path2.CopyTo(destination.Slice(path1.Length + (needsSeparator ? 1 : 0))); + + charsWritten = charsNeeded; + return true; + } + + private static string CombineInternal(ReadOnlySpan first, ReadOnlySpan second) { if (first.Length == 0) return second.Length == 0 @@ -400,10 +463,10 @@ private static string CombineNoChecks(ReadOnlySpan first, ReadOnlySpan first, ReadOnlySpan second, ReadOnlySpan third) - { - if (first.Length == 0) - return CombineNoChecks(second, third); - if (second.Length == 0) - return CombineNoChecks(first, third); - if (third.Length == 0) - return CombineNoChecks(first, second); - - if (IsPathRooted(third)) - return new string(third); - if (IsPathRooted(second)) - return CombineNoChecks(second, third); - - return CombineNoChecksInternal(first, second, third); - } - - private static string CombineNoChecks(string first, string second, string third) + private static string CombineInternal(string first, string second, string third) { if (string.IsNullOrEmpty(first)) - return CombineNoChecks(second, third); + return CombineInternal(second, third); if (string.IsNullOrEmpty(second)) - return CombineNoChecks(first, third); + return CombineInternal(first, third); if (string.IsNullOrEmpty(third)) - return CombineNoChecks(first, second); + return CombineInternal(first, second); if (IsPathRooted(third.AsSpan())) return third; if (IsPathRooted(second.AsSpan())) - return CombineNoChecks(second, third); - - return CombineNoChecksInternal(first, second, third); - } - - private static string CombineNoChecks(ReadOnlySpan first, ReadOnlySpan second, ReadOnlySpan third, ReadOnlySpan fourth) - { - if (first.Length == 0) - return CombineNoChecks(second, third, fourth); - if (second.Length == 0) - return CombineNoChecks(first, third, fourth); - if (third.Length == 0) - return CombineNoChecks(first, second, fourth); - if (fourth.Length == 0) - return CombineNoChecks(first, second, third); - - if (IsPathRooted(fourth)) - return new string(fourth); - if (IsPathRooted(third)) - return CombineNoChecks(third, fourth); - if (IsPathRooted(second)) - return CombineNoChecks(second, third, fourth); + return CombineInternal(second, third); - return CombineNoChecksInternal(first, second, third, fourth); + return JoinInternal(first, second, third); } - private static string CombineNoChecks(string first, string second, string third, string fourth) + private static string CombineInternal(string first, string second, string third, string fourth) { if (string.IsNullOrEmpty(first)) - return CombineNoChecks(second, third, fourth); + return CombineInternal(second, third, fourth); if (string.IsNullOrEmpty(second)) - return CombineNoChecks(first, third, fourth); + return CombineInternal(first, third, fourth); if (string.IsNullOrEmpty(third)) - return CombineNoChecks(first, second, fourth); + return CombineInternal(first, second, fourth); if (string.IsNullOrEmpty(fourth)) - return CombineNoChecks(first, second, third); + return CombineInternal(first, second, third); if (IsPathRooted(fourth.AsSpan())) return fourth; if (IsPathRooted(third.AsSpan())) - return CombineNoChecks(third, fourth); + return CombineInternal(third, fourth); if (IsPathRooted(second.AsSpan())) - return CombineNoChecks(second, third, fourth); + return CombineInternal(second, third, fourth); - return CombineNoChecksInternal(first, second, third, fourth); + return JoinInternal(first, second, third, fourth); } - private unsafe static string CombineNoChecksInternal(ReadOnlySpan first, ReadOnlySpan second) + private unsafe static string JoinInternal(ReadOnlySpan first, ReadOnlySpan second) { Debug.Assert(first.Length > 0 && second.Length > 0, "should have dealt with empty paths"); @@ -515,7 +540,7 @@ private unsafe static string CombineNoChecksInternal(ReadOnlySpan first, R } } - private unsafe static string CombineNoChecksInternal(ReadOnlySpan first, ReadOnlySpan second, ReadOnlySpan third) + private unsafe static string JoinInternal(ReadOnlySpan first, ReadOnlySpan second, ReadOnlySpan third) { Debug.Assert(first.Length > 0 && second.Length > 0 && third.Length > 0, "should have dealt with empty paths"); @@ -543,7 +568,7 @@ private unsafe static string CombineNoChecksInternal(ReadOnlySpan first, R } } - private unsafe static string CombineNoChecksInternal(ReadOnlySpan first, ReadOnlySpan second, ReadOnlySpan third, ReadOnlySpan fourth) + private unsafe static string JoinInternal(ReadOnlySpan first, ReadOnlySpan second, ReadOnlySpan third, ReadOnlySpan fourth) { Debug.Assert(first.Length > 0 && second.Length > 0 && third.Length > 0 && fourth.Length > 0, "should have dealt with empty paths"); diff --git a/src/mscorlib/shared/System/IO/PathInternal.cs b/src/mscorlib/shared/System/IO/PathInternal.cs index 3eac1e74e808..73fe1d073c4d 100644 --- a/src/mscorlib/shared/System/IO/PathInternal.cs +++ b/src/mscorlib/shared/System/IO/PathInternal.cs @@ -10,8 +10,7 @@ internal static partial class PathInternal /// /// Returns true if the path ends in a directory separator. /// - internal static bool EndsInDirectorySeparator(string path) => - !string.IsNullOrEmpty(path) && IsDirectorySeparator(path[path.Length - 1]); + internal static bool EndsInDirectorySeparator(ReadOnlySpan path) => IsDirectorySeparator(path[path.Length - 1]); /// /// Get the common path length from the start of the string. From b71b0e9892dc6625e7c283273aa2112ed3cc5463 Mon Sep 17 00:00:00 2001 From: Jeremy Kuhne Date: Sun, 25 Feb 2018 20:20:13 -0800 Subject: [PATCH 2/6] Address feedback and fix a couple issues now that I've got tests running correctly. --- src/mscorlib/shared/System/IO/Path.cs | 24 +++++++------------ src/mscorlib/shared/System/IO/PathInternal.cs | 7 +++++- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/mscorlib/shared/System/IO/Path.cs b/src/mscorlib/shared/System/IO/Path.cs index f417b88eec1a..3584bed1d9d9 100644 --- a/src/mscorlib/shared/System/IO/Path.cs +++ b/src/mscorlib/shared/System/IO/Path.cs @@ -390,6 +390,8 @@ public static string Join(ReadOnlySpan path1, ReadOnlySpan path2) { if (path1.Length == 0) return path2.Length == 0 ? string.Empty : new string(path2); + if (path2.Length == 0) + return new string(path1); return JoinInternal(path1, path2); } @@ -411,31 +413,23 @@ public static string Join(ReadOnlySpan path1, ReadOnlySpan path2, Re public static bool TryJoin(Span destination, out int charsWritten, ReadOnlySpan path1, ReadOnlySpan path2) { charsWritten = 0; - if (path1.Length == 0) - { - if (destination.Length < path2.Length) - { - return false; - } - - path2.CopyTo(destination); - charsWritten = path2.Length; + if (path1.Length == 0 && path2.Length == 0) return true; - } - if (path2.Length == 0) + if (path1.Length == 0 || path2.Length == 0) { - if (destination.Length < path1.Length) + ref ReadOnlySpan pathToUse = ref path1.Length == 0 ? ref path2 : ref path1; + if (destination.Length < pathToUse.Length) { return false; } - path1.CopyTo(destination); - charsWritten = path1.Length; + pathToUse.CopyTo(destination); + charsWritten = pathToUse.Length; return true; } - bool needsSeparator = !(PathInternal.EndsInDirectorySeparator(path1) || PathInternal.EndsInDirectorySeparator(path2)); + bool needsSeparator = !(PathInternal.EndsInDirectorySeparator(path1) || PathInternal.StartsWithDirectorySeparator(path2)); int charsNeeded = path1.Length + path2.Length + (needsSeparator ? 1 : 0); if (destination.Length < charsNeeded) return false; diff --git a/src/mscorlib/shared/System/IO/PathInternal.cs b/src/mscorlib/shared/System/IO/PathInternal.cs index 73fe1d073c4d..f2f350ddd015 100644 --- a/src/mscorlib/shared/System/IO/PathInternal.cs +++ b/src/mscorlib/shared/System/IO/PathInternal.cs @@ -10,7 +10,12 @@ internal static partial class PathInternal /// /// Returns true if the path ends in a directory separator. /// - internal static bool EndsInDirectorySeparator(ReadOnlySpan path) => IsDirectorySeparator(path[path.Length - 1]); + internal static bool EndsInDirectorySeparator(ReadOnlySpan path) => path.Length > 0 && IsDirectorySeparator(path[path.Length - 1]); + + /// + /// Returns true if the path starts in a directory separator. + /// + internal static bool StartsWithDirectorySeparator(ReadOnlySpan path) => path.Length > 0 && IsDirectorySeparator(path[0]); /// /// Get the common path length from the start of the string. From 6e07cb4683ed3874ed529b722ec4db84a454dbcc Mon Sep 17 00:00:00 2001 From: Jeremy Kuhne Date: Tue, 27 Feb 2018 13:18:13 -0800 Subject: [PATCH 3/6] Update per final API approval --- src/mscorlib/shared/System/IO/Path.cs | 38 +++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/src/mscorlib/shared/System/IO/Path.cs b/src/mscorlib/shared/System/IO/Path.cs index 3584bed1d9d9..1b08d9f36343 100644 --- a/src/mscorlib/shared/System/IO/Path.cs +++ b/src/mscorlib/shared/System/IO/Path.cs @@ -389,7 +389,7 @@ public static string Combine(params string[] paths) public static string Join(ReadOnlySpan path1, ReadOnlySpan path2) { if (path1.Length == 0) - return path2.Length == 0 ? string.Empty : new string(path2); + return new string(path2); if (path2.Length == 0) return new string(path1); @@ -410,7 +410,7 @@ public static string Join(ReadOnlySpan path1, ReadOnlySpan path2, Re return JoinInternal(path1, path2, path3); } - public static bool TryJoin(Span destination, out int charsWritten, ReadOnlySpan path1, ReadOnlySpan path2) + public static bool TryJoin(ReadOnlySpan path1, ReadOnlySpan path2, Span destination, out int charsWritten) { charsWritten = 0; if (path1.Length == 0 && path2.Length == 0) @@ -444,6 +444,40 @@ public static bool TryJoin(Span destination, out int charsWritten, ReadOnl return true; } + public static bool TryJoin(ReadOnlySpan path1, ReadOnlySpan path2, ReadOnlySpan path3, Span destination, out int charsWritten) + { + charsWritten = 0; + if (path1.Length == 0 && path2.Length == 0 && path3.Length == 0) + return true; + + if (path1.Length == 0) + return TryJoin(path2, path3, destination, out charsWritten); + if (path2.Length == 0) + return TryJoin(path1, path3, destination, out charsWritten); + if (path3.Length == 0) + return TryJoin(path1, path2, destination, out charsWritten); + + int neededSeparators = PathInternal.EndsInDirectorySeparator(path1) || PathInternal.StartsWithDirectorySeparator(path2) ? 0 : 1; + bool needsSecondSeparator = !(PathInternal.EndsInDirectorySeparator(path2) || PathInternal.StartsWithDirectorySeparator(path3)); + if (needsSecondSeparator) + neededSeparators++; + + int charsNeeded = path1.Length + path2.Length + path3.Length + neededSeparators; + if (destination.Length < charsNeeded) + return false; + + bool result = TryJoin(path1, path2, destination, out charsWritten); + Debug.Assert(result, "should never fail joining first two paths"); + + if (needsSecondSeparator) + destination[charsWritten++] = DirectorySeparatorChar; + + path3.CopyTo(destination.Slice(charsWritten)); + charsWritten += path3.Length; + + return true; + } + private static string CombineInternal(ReadOnlySpan first, ReadOnlySpan second) { if (first.Length == 0) From a6fc5c92509542738f9d8c6e0d870ecbe83b2563 Mon Sep 17 00:00:00 2001 From: Jeremy Kuhne Date: Tue, 27 Feb 2018 13:50:28 -0800 Subject: [PATCH 4/6] Fix Unix, remove redundant helper. --- src/mscorlib/shared/System/IO/Path.Unix.cs | 2 +- src/mscorlib/shared/System/IO/Path.Windows.cs | 10 +--------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/src/mscorlib/shared/System/IO/Path.Unix.cs b/src/mscorlib/shared/System/IO/Path.Unix.cs index 12949308c87f..fd24cc810c24 100644 --- a/src/mscorlib/shared/System/IO/Path.Unix.cs +++ b/src/mscorlib/shared/System/IO/Path.Unix.cs @@ -61,7 +61,7 @@ public static string GetFullPath(string path, string basePath) if (IsPathFullyQualified(path)) return GetFullPath(path); - return GetFullPath(CombineNoChecks(basePath, path)); + return GetFullPath(CombineInternal(basePath, path)); } private static string RemoveLongPathPrefix(string path) diff --git a/src/mscorlib/shared/System/IO/Path.Windows.cs b/src/mscorlib/shared/System/IO/Path.Windows.cs index 5083b5046087..0e0785a76def 100644 --- a/src/mscorlib/shared/System/IO/Path.Windows.cs +++ b/src/mscorlib/shared/System/IO/Path.Windows.cs @@ -215,20 +215,12 @@ internal static ReadOnlySpan GetVolumeName(ReadOnlySpan path) return TrimEndingDirectorySeparator(root); // e.g. "C:" } - /// - /// Returns true if the path ends in a directory separator. - /// - internal static bool EndsInDirectorySeparator(ReadOnlySpan path) - { - return path.Length > 0 && PathInternal.IsDirectorySeparator(path[path.Length - 1]); - } - /// /// Trims the ending directory separator if present. /// /// internal static ReadOnlySpan TrimEndingDirectorySeparator(ReadOnlySpan path) => - EndsInDirectorySeparator(path) ? + PathInternal.EndsInDirectorySeparator(path) ? path.Slice(0, path.Length - 1) : path; From 2bba548600bb04ad7640f6dd4f090e9675f4a0d3 Mon Sep 17 00:00:00 2001 From: Jeremy Kuhne Date: Tue, 27 Feb 2018 16:27:34 -0800 Subject: [PATCH 5/6] Merge and tweak join methods in GetFullPath(string, string) --- src/mscorlib/shared/System/IO/Path.Windows.cs | 13 ++++++++----- src/mscorlib/shared/System/IO/Path.cs | 1 - 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/mscorlib/shared/System/IO/Path.Windows.cs b/src/mscorlib/shared/System/IO/Path.Windows.cs index 0e0785a76def..01db27d453fc 100644 --- a/src/mscorlib/shared/System/IO/Path.Windows.cs +++ b/src/mscorlib/shared/System/IO/Path.Windows.cs @@ -80,26 +80,29 @@ public static string GetFullPath(string path, string basePath) // Path is current drive rooted i.e. starts with \: // "\Foo" and "C:\Bar" => "C:\Foo" // "\Foo" and "\\?\C:\Bar" => "\\?\C:\Foo" - combinedPath = CombineInternal(GetPathRoot(basePath), path.AsSpan().Slice(1)); + combinedPath = Join(GetPathRoot(basePath.AsSpan()), path); } else if (length >= 2 && PathInternal.IsValidDriveChar(path[0]) && path[1] == PathInternal.VolumeSeparatorChar) { // Drive relative paths Debug.Assert(length == 2 || !PathInternal.IsDirectorySeparator(path[2])); - if (StringSpanHelpers.Equals(GetVolumeName(path.AsSpan()), GetVolumeName(basePath.AsSpan()))) + if (StringSpanHelpers.Equals(GetVolumeName(path), GetVolumeName(basePath))) { // Matching root // "C:Foo" and "C:\Bar" => "C:\Bar\Foo" // "C:Foo" and "\\?\C:\Bar" => "\\?\C:\Bar\Foo" - combinedPath = CombineInternal(basePath, path.AsSpan().Slice(2)); + combinedPath = Join(basePath, path.AsSpan().Slice(2)); } else { // No matching root, root to specified drive // "D:Foo" and "C:\Bar" => "D:Foo" // "D:Foo" and "\\?\C:\Bar" => "\\?\D:\Foo" - combinedPath = PathInternal.IsDevice(basePath) ? CombineNoChecksInternal(basePath.AsSpan().Slice(0, 4), path.AsSpan().Slice(0, 2), @"\", path.AsSpan().Slice(2)) : path.Insert(2, "\\"); + + combinedPath = PathInternal.IsDevice(basePath) + ? JoinInternal(basePath.AsSpan().Slice(0, 4), path.AsSpan().Slice(0, 2), @"\", path.AsSpan().Slice(2)) + : path.Insert(2, @"\"); } } else @@ -107,7 +110,7 @@ public static string GetFullPath(string path, string basePath) // "Simple" relative path // "Foo" and "C:\Bar" => "C:\Bar\Foo" // "Foo" and "\\?\C:\Bar" => "\\?\C:\Bar\Foo" - combinedPath = CombineInternal(basePath, path); + combinedPath = JoinInternal(basePath, path); } // Device paths are normalized by definition, so passing something of this format diff --git a/src/mscorlib/shared/System/IO/Path.cs b/src/mscorlib/shared/System/IO/Path.cs index 1b08d9f36343..41ae1cd0bef0 100644 --- a/src/mscorlib/shared/System/IO/Path.cs +++ b/src/mscorlib/shared/System/IO/Path.cs @@ -262,7 +262,6 @@ public static bool IsPathFullyQualified(ReadOnlySpan path) return !PathInternal.IsPartiallyQualified(path); } - /// /// Tests if a path's file name includes a file extension. A trailing period /// is not considered an extension. From 3cb5dec377eba27ec53dbc5f9a54ae85def33a58 Mon Sep 17 00:00:00 2001 From: Jeremy Kuhne Date: Tue, 27 Feb 2018 16:36:22 -0800 Subject: [PATCH 6/6] Tweak again --- src/mscorlib/shared/System/IO/Path.Windows.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/mscorlib/shared/System/IO/Path.Windows.cs b/src/mscorlib/shared/System/IO/Path.Windows.cs index 01db27d453fc..b921db9e615f 100644 --- a/src/mscorlib/shared/System/IO/Path.Windows.cs +++ b/src/mscorlib/shared/System/IO/Path.Windows.cs @@ -99,10 +99,11 @@ public static string GetFullPath(string path, string basePath) // No matching root, root to specified drive // "D:Foo" and "C:\Bar" => "D:Foo" // "D:Foo" and "\\?\C:\Bar" => "\\?\D:\Foo" - - combinedPath = PathInternal.IsDevice(basePath) - ? JoinInternal(basePath.AsSpan().Slice(0, 4), path.AsSpan().Slice(0, 2), @"\", path.AsSpan().Slice(2)) - : path.Insert(2, @"\"); + combinedPath = !PathInternal.IsDevice(basePath) + ? path.Insert(2, @"\") + : length == 2 + ? JoinInternal(basePath.AsSpan().Slice(0, 4), path, @"\") + : JoinInternal(basePath.AsSpan().Slice(0, 4), path.AsSpan().Slice(0, 2), @"\", path.AsSpan().Slice(2)); } } else