diff --git a/csharp/PhoneNumbers/PhoneNumberUtil.cs b/csharp/PhoneNumbers/PhoneNumberUtil.cs
index 79ce721a..b4d32300 100644
--- a/csharp/PhoneNumbers/PhoneNumberUtil.cs
+++ b/csharp/PhoneNumbers/PhoneNumberUtil.cs
@@ -869,8 +869,10 @@ public static string GetCountryMobileToken(int countryCallingCode)
/// should be stripped from the number. If this is false, they
/// will be left unchanged in the number.
/// The normalized string version of the phone number.
+#if !(NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER)
private static string NormalizeHelper(string number, Func normalizationReplacements, bool removeNonMatches)
=> NormalizeHelper(new StringBuilder(number), normalizationReplacements, removeNonMatches).ToString();
+#endif
private static StringBuilder NormalizeHelper(StringBuilder number, Func normalizationReplacements, bool removeNonMatches)
{
@@ -985,27 +987,26 @@ private static bool DescHasData(PhoneNumberDesc desc)
|| desc.HasNationalNumberPattern;
}
+ // Pre-filtered: excludes FIXED_LINE_OR_MOBILE (convenience aggregate) and UNKNOWN (non-type).
+ private static readonly PhoneNumberType[] s_checkablePhoneNumberTypes =
+ [
+ PhoneNumberType.FIXED_LINE, PhoneNumberType.MOBILE, PhoneNumberType.TOLL_FREE,
+ PhoneNumberType.PREMIUM_RATE, PhoneNumberType.SHARED_COST, PhoneNumberType.VOIP,
+ PhoneNumberType.PERSONAL_NUMBER, PhoneNumberType.PAGER, PhoneNumberType.UAN,
+ PhoneNumberType.VOICEMAIL,
+ ];
+
///
/// Returns the types we have metadata for based on the PhoneMetadata object passed in, which must
/// be non-null.
///
- ///
- ///
private HashSet GetSupportedTypesForMetadata(PhoneMetadata metadata)
{
var types = new HashSet();
- foreach (PhoneNumberType type in Enum.GetValues(typeof(PhoneNumberType)))
+ foreach (var type in s_checkablePhoneNumberTypes)
{
- if (type == PhoneNumberType.FIXED_LINE_OR_MOBILE || type == PhoneNumberType.UNKNOWN)
- {
- // Never return FIXED_LINE_OR_MOBILE (it is a convenience type, and represents that a
- // particular number type can't be determined) or UNKNOWN (the non-type).
- continue;
- }
if (DescHasData(GetNumberDescByType(metadata, type)))
- {
types.Add(type);
- }
}
return types;
}
@@ -2305,6 +2306,14 @@ private int MaybeExtractCountryCode(string number, PhoneMetadata defaultRegionMe
nationalNumber, keepRawInput, phoneNumber);
}
+ private static bool StringBuilderStartsWith(StringBuilder sb, string value)
+ {
+ if (sb.Length < value.Length) return false;
+ for (var i = 0; i < value.Length; i++)
+ if (sb[i] != value[i]) return false;
+ return true;
+ }
+
// Same as the string overload above, but takes a StringBuilder workspace directly so callers
// that already hold a StringBuilder can avoid an extra string<->StringBuilder round-trip.
// The fullNumber buffer is always mutated (Normalize is unconditional, and any leading '+' or
@@ -2355,9 +2364,10 @@ private int MaybeExtractCountryCode(StringBuilder fullNumber, PhoneMetadata defa
// before and after.
var defaultCountryCode = defaultRegionMetadata.CountryCode;
var defaultCountryCodeString = defaultCountryCode.ToString();
- var normalizedNumber = fullNumber.ToString();
- if (normalizedNumber.StartsWith(defaultCountryCodeString, StringComparison.Ordinal))
+ // Avoid the ToString() allocation unless the prefix actually matches (common case: it won't).
+ if (StringBuilderStartsWith(fullNumber, defaultCountryCodeString))
{
+ var normalizedNumber = fullNumber.ToString();
var potentialNationalNumberString = normalizedNumber.Substring(defaultCountryCodeString.Length);
// Use a separate buffer for the strip-CC trial so callers that share fullNumber as
// their workspace are not corrupted when this branch decides not to commit.
@@ -2371,7 +2381,7 @@ private int MaybeExtractCountryCode(StringBuilder fullNumber, PhoneMetadata defa
var validNumberPattern = generalDesc.GetNationalNumberPattern();
if ((!validNumberPattern.IsMatchAll(normalizedNumber) &&
validNumberPattern.IsMatchAll(potentialNationalNumberString)) ||
- TestNumberLength(normalizedNumber.Length, defaultRegionMetadata) == ValidationResult.TOO_LONG)
+ TestNumberLength(fullNumber.Length, defaultRegionMetadata) == ValidationResult.TOO_LONG)
{
nationalNumber.Append(potentialNationalNumberString);
if (keepRawInput)
@@ -2529,7 +2539,11 @@ static string MaybeStripExtension(StringBuilder number, string numberString)
var m = ExtnPattern().Match(numberString);
// If we find a potential extension, and the number preceding this is a viable number, we assume
// it is an extension.
+#if NET7_0_OR_GREATER
+ if (m.Success && IsViablePhoneNumberSpan(numberString.AsSpan(0, m.Index)))
+#else
if (m.Success && IsViablePhoneNumber(numberString.Substring(0, m.Index)))
+#endif
{
// The numbers are captured into groups in the regular expression.
for (int i = 1, length = m.Groups.Count; i < length; i++)
diff --git a/csharp/PhoneNumbers/PhoneNumberUtil.net.cs b/csharp/PhoneNumbers/PhoneNumberUtil.net.cs
index e8105231..91d631be 100644
--- a/csharp/PhoneNumbers/PhoneNumberUtil.net.cs
+++ b/csharp/PhoneNumbers/PhoneNumberUtil.net.cs
@@ -606,6 +606,15 @@ private static void NormalizeHelper(ref Span span,
}
}
+ private static string NormalizeHelper(string number, Func normalizationReplacements, bool removeNonMatches)
+ {
+ if (number.Length == 0) return string.Empty;
+ Span result = stackalloc char[number.Length];
+ var resultLength = 0;
+ NormalizeHelper(ref result, ref resultLength, number, normalizationReplacements, removeNonMatches);
+ return new string(result.Slice(0, resultLength));
+ }
+
private static bool IsValidAlphaPhone(string number)
{
for (int alpha = 0, i = 0; i < number.Length; i++)
@@ -750,6 +759,14 @@ private void PrefixNumberWithCountryCallingCode(ref Span span,
return;
}
}
+
+#if NET7_0_OR_GREATER
+ internal static bool IsViablePhoneNumberSpan(ReadOnlySpan number)
+ {
+ if (number.Length < MIN_LENGTH_FOR_NSN) return false;
+ return ValidPhoneNumber().IsMatch(number);
+ }
+#endif
}
}
#endif