Description
ProjectPathProvider.GetRelativePath() creates two Uri objects and performs URI escaping/unescaping on every call. This method is called inside the inner loop of ProjectPathViolationDetector.GetViolationsFrom() (line 48) — once per reference × matching rule combination.
Affected Files
src/ReferenceCop/Providers/ProjectPathProvider.cs — GetRelativePath(), lines 35-36 (new Uri() allocations)
src/ReferenceCop/Detectors/ProjectPathViolationDetector.cs — line 48 (inner loop call site)
Impact
- Allocation pressure: Two
Uri allocations + Path.GetFullPath + Uri.UnescapeDataString per inner loop iteration. For 20 rules × 50 references, that's up to 2,000 Uri objects per project build.
- Redundant computation: The
repositoryRoot URI (line 35) is the same every call but recomputed each time. The projectFilePath URI for the same reference is also recomputed if multiple rules match.
- GC pressure:
Uri is a relatively heavy object (parses/normalizes the URI string internally), and these short-lived allocations add GC pressure during build.
Suggested Optimization
- Cache the repository root URI as a field computed once in the constructor:
public class ProjectPathProvider : IProjectPathProvider
{
private readonly Uri repositoryRootUri;
private readonly string repositoryRoot;
public ProjectPathProvider(string repositoryRoot)
{
this.repositoryRoot = repositoryRoot ?? throw new ArgumentNullException(nameof(repositoryRoot));
var normalized = Path.GetFullPath(repositoryRoot)
.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)
+ Path.DirectorySeparatorChar;
this.repositoryRootUri = new Uri(normalized);
}
public string GetRelativePath(string projectFilePath)
{
var fullPath = Path.GetFullPath(projectFilePath);
var projectUri = new Uri(fullPath);
var relativePath = Uri.UnescapeDataString(
this.repositoryRootUri.MakeRelativeUri(projectUri).ToString());
return relativePath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
}
}
- Consider using
Path.GetRelativePath (available since .NET Standard 2.1 / .NET Core 2.0), which avoids Uri altogether:
public string GetRelativePath(string projectFilePath)
{
return Path.GetRelativePath(this.repositoryRoot, projectFilePath);
}
This eliminates all Uri allocations and is the idiomatic .NET approach for relative path computation.
Description
ProjectPathProvider.GetRelativePath()creates twoUriobjects and performs URI escaping/unescaping on every call. This method is called inside the inner loop ofProjectPathViolationDetector.GetViolationsFrom()(line 48) — once per reference × matching rule combination.Affected Files
src/ReferenceCop/Providers/ProjectPathProvider.cs—GetRelativePath(), lines 35-36 (new Uri()allocations)src/ReferenceCop/Detectors/ProjectPathViolationDetector.cs— line 48 (inner loop call site)Impact
Uriallocations +Path.GetFullPath+Uri.UnescapeDataStringper inner loop iteration. For 20 rules × 50 references, that's up to 2,000Uriobjects per project build.repositoryRootURI (line 35) is the same every call but recomputed each time. TheprojectFilePathURI for the same reference is also recomputed if multiple rules match.Uriis a relatively heavy object (parses/normalizes the URI string internally), and these short-lived allocations add GC pressure during build.Suggested Optimization
Path.GetRelativePath(available since .NET Standard 2.1 / .NET Core 2.0), which avoidsUrialtogether:This eliminates all
Uriallocations and is the idiomatic .NET approach for relative path computation.