Background and motivation
Presciently, the api review for MemoryExtensions.CommonPrefixLength<T> noted:
- We might need to add an overload that is specific to char and takes StringComparison
There are two major reasons to add this overload:
- It enables vectorization when doing an ordinalignorecase comparison. (Performance)
- It no longer requires you to implement your own
IEqualityComparer<char> to gain the desired behavior. (Usability)
Currently, if you are looking for a common prefix case-insensitively among strings, you have to entirely give up on vectorization by passing an IEqualityComparer<char> (which you also have to define yourself) which will then be called into for every char.
Prior art is the MemoryExtensions.Equals extension on spans of chars that takes a StringComparison parameter. The rationale that applied there, applies here.
API Proposal
namespace System;
public static class MemoryExtensions
{
extension(ReadOnlySpan<char> span)
{
public int CommonPrefixLength(ReadOnlySpan<char> other, StringComparison comparisonType);
}
}
ℹ️ An extension is not defined on Span<T>. This is due to the C# 14 language feature first-class spans which enables the ReadOnlySpan<T> extensions to appear when dotting off any Span<T> expression.
API Usage
Finding a common base folder among a bunch of file and folder paths, where paths are case-insensitive:
public static string GetCommonPath(params IEnumerable<string> paths)
{
using var enumerator = paths.GetEnumerator();
if (!enumerator.MoveNext())
return string.Empty;
var firstString = enumerator.Current;
var commonLength = firstString.Length;
if (commonLength == 0)
return firstString;
var first = firstString.AsSpan();
while (enumerator.MoveNext())
{
var current = enumerator.Current;
var newLength = first[..commonLength].CommonPrefixLength(current, StringComparison.OrdinalIgnoreCase);
var atSegmentBoundary =
(newLength == commonLength || first[newLength] is '/' or '\\')
&& (newLength == current.Length || current[newLength] is '/' or '\\');
commonLength = atSegmentBoundary
? newLength
: first[..newLength].LastIndexOfAny('/', '\\');
if (commonLength <= 0)
return "";
}
return commonLength == first.Length ? firstString : first[..commonLength].ToString();
}
Alternative Designs
No response
Risks
No response
Background and motivation
Presciently, the api review for
MemoryExtensions.CommonPrefixLength<T>noted:There are two major reasons to add this overload:
IEqualityComparer<char>to gain the desired behavior. (Usability)Currently, if you are looking for a common prefix case-insensitively among strings, you have to entirely give up on vectorization by passing an
IEqualityComparer<char>(which you also have to define yourself) which will then be called into for every char.Prior art is the
MemoryExtensions.Equalsextension on spans of chars that takes aStringComparisonparameter. The rationale that applied there, applies here.API Proposal
ℹ️ An extension is not defined on
Span<T>. This is due to the C# 14 language feature first-class spans which enables theReadOnlySpan<T>extensions to appear when dotting off anySpan<T>expression.API Usage
Finding a common base folder among a bunch of file and folder paths, where paths are case-insensitive:
Alternative Designs
No response
Risks
No response