Repository navigation
Port the BCL string algorithm for Path.GetRelativePath - #696
Merged
Merged
Conversation
This was referenced Oct 12, 2026
This was referenced Oct 13, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The
Path.GetRelativePathpolyfill (.NET Framework and netstandard2.0) was implemented throughUri.MakeRelativeUri, which costs twoUriinstances plus escaping and unescaping on every call, and does not give the same answers as the BCL.Change
Port the BCL's string algorithm: after
Path.GetFullPathon both inputs, compare the roots, find the common prefix length (backed up to a segment boundary), emit one..per remaining segment of the "from" path, then append the rest of the "to" path. When no..segments are needed the result is a singleSubstring.Roots are still taken from
Path.GetPathRoot. The comparison is now ordinal-ignore-case on Windows and macOS and ordinal elsewhere, as in the BCL (the old code was hard-coded to ignore case, which is wrong for a netstandard2.0 library running on Linux).Behaviour changes (all towards the BCL)
Of the 76 input pairs in the new test table, the old implementation disagreed with net10 on 19. They fall into three groups:
relativeTopathC:\aC:\a\b\bb\C:\a\b\cC:\a\x\y\..\..\x\y..\..\x\y\C:\baseC:\base\a%2Dba-ba%2DbC:\baseC:\base\%41A%41C:\ba%2Dse\xC:\ba-se\y..\y..\..\ba-se\yC:\ÉC:\é\x..\é\xxpathwas dropped; it is now preservedUridoes not need to keep escaped (%2D,%41, ...) were unescaped, so the result named a different file, and two different directories could be treated as the same oneMeasurements
net48, x64, Release, 500,000 calls each. Both versions start with the same two
Path.GetFullPathcalls, which cost roughly 600-850 ns of each figure and allocate nothing here...\..\Microsoft\Edge\Application)src\Library\File.cs).)The last two rows never reached
Uribefore, so they are within noise of each other; the remaining 64 B is the twoPath.GetPathRootstrings.Tests
GetRelativePath_MatchesBcl: a table of 76 input pairs whose expected values were produced byPath.GetRelativePathon net10 on Windows: same path with and without trailing separators on either side, children, parents, siblings, partial segment matches (C:\Foodie/C:\Foobar), the drive root on either side, different drives, UNC shares (same share, different share, different server, UNC against a drive), case differences in ASCII and non-ASCII, percent sequences,#,&,+, spaces, non-ASCII names, forward slashes,.and..segments, and relative inputsGetRelativePath_ResultCombinesBackToPath: for every pair, combining the result withrelativeToleads back topathGetRelativePath_RelativeInputsUseCurrentDirectoryThe tests run on every target, so on netcoreapp3.1 and later they check the table against the real BCL as well.