In the common case where it need to replace a non-empty extension with a non-empty extension, it currently incurs a substring without the original extension prior to then concatenating with the new extension. This PR avoids that.
(As the Path implementation is in corelib, this uses string.FastAllocateString and then formats into it with a span; if we wanted to avoid that, string.Create could also be used.)
* Add internal String.Concat overloads for spans
* Use span-based string.Concat overloads in several places
Debug.Assert(!GlobalizationMode.Invariant);
- string alternateSortName = string.Empty;
string realNameBuffer = _sRealName;
// Basic validation
}
// Replace _ (alternate sort) with @collation= for ICU
+ ReadOnlySpan<char> alternateSortName = default;
int index = realNameBuffer.IndexOf('_');
if (index > 0)
{
if (index >= (realNameBuffer.Length - 1) // must have characters after _
- || realNameBuffer.Substring(index + 1).Contains('_')) // only one _ allowed
+ || realNameBuffer.IndexOf('_', index + 1) >= 0) // only one _ allowed
{
return false; // fail
}
- alternateSortName = realNameBuffer.Substring(index + 1);
- realNameBuffer = realNameBuffer.Substring(0, index) + ICU_COLLATION_KEYWORD + alternateSortName;
+ alternateSortName = realNameBuffer.AsSpan(index + 1);
+ realNameBuffer = string.Concat(realNameBuffer.AsSpan(0, index), ICU_COLLATION_KEYWORD, alternateSortName);
}
// Get the locale name from ICU
index = _sWindowsName.IndexOf(ICU_COLLATION_KEYWORD, StringComparison.Ordinal);
if (index >= 0)
{
- _sName = _sWindowsName.Substring(0, index) + "_" + alternateSortName;
+ _sName = string.Concat(_sWindowsName.AsSpan(0, index), "_", alternateSortName);
}
else
{
if (this.SENGLISHLANGUAGE[this.SENGLISHLANGUAGE.Length - 1] == ')')
{
// "Azeri (Latin)" + "Azerbaijan" -> "Azeri (Latin, Azerbaijan)"
- _sEnglishDisplayName =
- this.SENGLISHLANGUAGE.Substring(0, _sEnglishLanguage.Length - 1) +
- ", " + this.SENGCOUNTRY + ")";
+ _sEnglishDisplayName = string.Concat(
+ this.SENGLISHLANGUAGE.AsSpan(0, _sEnglishLanguage.Length - 1),
+ ", ",
+ this.SENGCOUNTRY,
+ ")");
}
else
{
sep = "";
}
- time = time.Substring(0, j) + sep + time.Substring(endIndex);
+ time = string.Concat(time.AsSpan(0, j), sep, time.AsSpan(endIndex));
break;
case 'm':
case 'H':
// This is determined in DateTimeFormatInfoScanner. Use this flag to determine if we should treat date separator as ignorable symbol.
bool useDateSepAsIgnorableSymbol = false;
- string monthPostfix = null;
if (dateWords != null)
{
// There are DateWords. It could be a real date word (such as "de"), or a monthPostfix.
// This is a month postfix
case DateTimeFormatInfoScanner.MonthPostfixChar:
// Get the real month postfix.
- monthPostfix = dateWords[i].Substring(1);
+ ReadOnlySpan<char> monthPostfix = dateWords[i].AsSpan(1);
// Add the month name + postfix into the token.
AddMonthNames(temp, monthPostfix);
break;
InsertHash(temp, this.DateSeparator, TokenType.SEP_Date, 0);
}
// Add the regular month names.
- AddMonthNames(temp, null);
+ AddMonthNames(temp);
// Add the abbreviated month names.
for (int i = 1; i <= 13; i++)
return (temp);
}
- private void AddMonthNames(TokenHashValue[] temp, string monthPostfix)
+ private void AddMonthNames(TokenHashValue[] temp, ReadOnlySpan<char> monthPostfix = default)
{
for (int i = 1; i <= 13; i++)
{
- string str;
//str = internalGetMonthName(i, MonthNameStyles.Regular, false);
// We have to call public methods here to work with inherited DTFI.
// Insert the month name first, so that they are at the front of abbreviated
// month names.
- str = GetMonthName(i);
+ string str = GetMonthName(i);
if (str.Length > 0)
{
- if (monthPostfix != null)
+ if (!monthPostfix.IsEmpty)
{
// Insert the month name with the postfix first, so it can be matched first.
- InsertHash(temp, str + monthPostfix, TokenType.MonthToken, i);
+ InsertHash(temp, string.Concat(str, monthPostfix), TokenType.MonthToken, i);
}
else
{
}
if (str[str.Length - 1] == '.')
{
- // Old version ignore the trialing dot in the date words. Support this as well.
+ // Old version ignore the trailing dot in the date words. Support this as well.
string strWithoutDot = str.Substring(0, str.Length - 1);
if (!m_dateWords.Contains(strWithoutDot))
{
// is null, any existing extension is removed from path.
public static string ChangeExtension(string path, string extension)
{
- if (path != null)
+ if (path == null)
+ return null;
+
+ int subLength = path.Length;
+ if (subLength == 0)
+ return string.Empty;
+
+ for (int i = path.Length - 1; i >= 0; i--)
{
- string s = path;
- for (int i = path.Length - 1; i >= 0; i--)
+ char ch = path[i];
+
+ if (ch == '.')
{
- char ch = path[i];
- if (ch == '.')
- {
- s = path.Substring(0, i);
- break;
- }
- if (PathInternal.IsDirectorySeparator(ch)) break;
+ subLength = i;
+ break;
}
- if (extension != null && path.Length != 0)
+ if (PathInternal.IsDirectorySeparator(ch))
{
- s = (extension.Length == 0 || extension[0] != '.') ?
- s + "." + extension :
- s + extension;
+ break;
}
+ }
- return s;
+ if (extension == null)
+ {
+ return path.Substring(0, subLength);
}
- return null;
+
+ ReadOnlySpan<char> subpath = path.AsSpan(0, subLength);
+ return extension.StartsWith('.') ?
+ string.Concat(subpath, extension) :
+ string.Concat(subpath, ".", extension);
}
/// <summary>
return result;
}
+ // TODO: Expose publicly: https://github.com/dotnet/corefx/issues/34330.
+ internal static string Concat(ReadOnlySpan<char> str0, ReadOnlySpan<char> str1)
+ {
+ string result = FastAllocateString(checked(str0.Length + str1.Length));
+ Span<char> resultSpan = new Span<char>(ref result.GetRawStringData(), result.Length);
+
+ str0.CopyTo(resultSpan);
+ str1.CopyTo(resultSpan.Slice(str0.Length));
+
+ return result;
+ }
+
+ // TODO: Expose publicly: https://github.com/dotnet/corefx/issues/34330.
+ internal static string Concat(ReadOnlySpan<char> str0, ReadOnlySpan<char> str1, ReadOnlySpan<char> str2)
+ {
+ string result = FastAllocateString(checked(str0.Length + str1.Length + str2.Length));
+ Span<char> resultSpan = new Span<char>(ref result.GetRawStringData(), result.Length);
+
+ str0.CopyTo(resultSpan);
+ resultSpan = resultSpan.Slice(str0.Length);
+
+ str1.CopyTo(resultSpan);
+ resultSpan = resultSpan.Slice(str1.Length);
+
+ str2.CopyTo(resultSpan);
+
+ return result;
+ }
+
+ // TODO: Expose publicly: https://github.com/dotnet/corefx/issues/34330.
+ internal static string Concat(ReadOnlySpan<char> str0, ReadOnlySpan<char> str1, ReadOnlySpan<char> str2, ReadOnlySpan<char> str3)
+ {
+ string result = FastAllocateString(checked(str0.Length + str1.Length + str2.Length + str3.Length));
+ Span<char> resultSpan = new Span<char>(ref result.GetRawStringData(), result.Length);
+
+ str0.CopyTo(resultSpan);
+ resultSpan = resultSpan.Slice(str0.Length);
+
+ str1.CopyTo(resultSpan);
+ resultSpan = resultSpan.Slice(str1.Length);
+
+ str2.CopyTo(resultSpan);
+ resultSpan = resultSpan.Slice(str2.Length);
+
+ str3.CopyTo(resultSpan);
+
+ return result;
+ }
+
public static string Concat(params string[] values)
{
if (values == null)