From 27b0af85930e1cff80c6e300101b2c62d30b1744 Mon Sep 17 00:00:00 2001 From: Thomas Ryan Date: Mon, 4 Nov 2024 23:22:29 -0800 Subject: [PATCH] Improved performance using readonly, records, ref, and structs. --- PhoneNumbersNA.Benchmark/Program.cs | 12 +- PhoneNumbersNA.Test/Unit.cs | 353 +++++++-------- PhoneNumbersNA/AreaCode.cs | 646 +++++++++++---------------- PhoneNumbersNA/PhoneNumbersNA.csproj | 2 +- 4 files changed, 440 insertions(+), 573 deletions(-) diff --git a/PhoneNumbersNA.Benchmark/Program.cs b/PhoneNumbersNA.Benchmark/Program.cs index 8839b55..241e2ca 100644 --- a/PhoneNumbersNA.Benchmark/Program.cs +++ b/PhoneNumbersNA.Benchmark/Program.cs @@ -12,8 +12,8 @@ public class Data { // https://nationalnanpa.com/contact_us/NANP_Country_Contacts.pdf - readonly string[] NANPContactsSmall = new string[] - { + readonly string[] NANPContactsSmall = + [ "264 497-2651", "264-497-3651", "268- 468-4616", "242-393-0234", "242-393-0153", "246- 535-2502", "441-405-6000", "441-474-6048", "284-468-2183", "284-468-3090", "284-468-4165", "284-494- 6786", @@ -27,10 +27,10 @@ public class Data "784-457-2279","784-457-2834","868-675-8288","868-674-1055", "649-946-1900","649-946-1119","202-418-1500","202-418-2825", "202-418-1525","202-418-1413","925-420-0340","571-363-3838", - }; + ]; - readonly string[] NANPContactsLarge = new string[] - { + readonly string[] NANPContactsLarge = + [ "264 497-2651", "264-497-3651", "268- 468-4616", "242-393-0234", "242-393-0153", "246- 535-2502", "441-405-6000", "441-474-6048", "284-468-2183", "284-468-3090", "284-468-4165", "284-494- 6786", @@ -161,7 +161,7 @@ public class Data "784-457-2279","784-457-2834","868-675-8288","868-674-1055", "649-946-1900","649-946-1119","202-418-1500","202-418-2825", "202-418-1525","202-418-1413","925-420-0340","571-363-3838", - }; + ]; readonly string NANPContactsStringSmall = "264 497-2651,264-497-3651,268- 468-4616,242-393-0234," + diff --git a/PhoneNumbersNA.Test/Unit.cs b/PhoneNumbersNA.Test/Unit.cs index ac56bf2..94c61fb 100644 --- a/PhoneNumbersNA.Test/Unit.cs +++ b/PhoneNumbersNA.Test/Unit.cs @@ -10,18 +10,12 @@ namespace PhoneNumbersNA.Test { - public class Unit + public class Unit(ITestOutputHelper output) { - readonly ITestOutputHelper output; - - public Unit(ITestOutputHelper output) - { - this.output = output; - } // https://nationalnanpa.com/contact_us/NANP_Country_Contacts.pdf - readonly string[] NANPContacts = new string[] - { + readonly string[] NANPContacts = + [ "264 497-2651", "264-497-3651", "268- 468-4616", "242-393-0234", "242-393-0153", "246- 535-2502", "441-405-6000", "441-474-6048", "284-468-2183", "284-468-3090", "284-468-4165", "284-494- 6786", @@ -35,26 +29,26 @@ public Unit(ITestOutputHelper output) "784-457-2279","784-457-2834","868-675-8288","868-674-1055", "649-946-1900","649-946-1119","202-418-1500","202-418-2825", "202-418-1525","202-418-1413","925-420-0340","571-363-3838", - }; + ]; - readonly string[] CenturyLinkShortCodes = new string[] - { + readonly string[] CenturyLinkShortCodes = + [ "67378", "58865", "275285", "30471" - }; + ]; readonly string manyNumbers = "+1 206-858-9310\r\n2024561414\r\n(206)858-8757\r\nRandom Gibberish that should be stripped"; - readonly string[] badNumbers = new string[] - { + readonly string[] badNumbers = + [ "5558675309", "0000000000", "1111111111", string.Empty, "Choose..." - }; + ]; [Fact] public void ExtractNumbers() { var numbers = manyNumbers.ExtractDialedNumbers(); - Assert.True(numbers.Any()); - Assert.True(numbers.Count() is 3); + Assert.NotEqual(0,numbers.Length); + Assert.True(numbers.Length is 3); foreach (var number in numbers) { var checkParse = PhoneNumber.TryParse(number, out var phoneNumber); @@ -64,11 +58,11 @@ public void ExtractNumbers() } numbers = string.Join(", ", badNumbers).ExtractDialedNumbers(); - Assert.False(numbers.Any()); + Assert.Equal(0, numbers.Length); numbers = AreaCode.ExtractDialedNumbers(manyNumbers.AsSpan()); - Assert.True(numbers.Any()); - Assert.True(numbers.Count() is 3); + Assert.NotEqual(0, numbers.Length); + Assert.True(numbers.Length is 3); foreach (var number in numbers) { var checkParse = PhoneNumber.TryParse(number, out var phoneNumber); @@ -78,18 +72,18 @@ public void ExtractNumbers() } numbers = AreaCode.ExtractDialedNumbers(string.Join(", ", badNumbers).AsSpan()); - Assert.False(numbers.Any()); + Assert.Equal(0, numbers.Length); var phoneNumbers = manyNumbers.ExtractPhoneNumbers(); - Assert.True(phoneNumbers.Any()); - Assert.True(phoneNumbers.Count() is 3); + Assert.NotEqual(0, phoneNumbers.Length); + Assert.True(phoneNumbers.Length is 3); foreach (var number in phoneNumbers) { Assert.True(number.IsValid()); } phoneNumbers = string.Join(", ", badNumbers).ExtractPhoneNumbers(); - Assert.False(phoneNumbers.Any()); + Assert.Equal(0, phoneNumbers.Length); } [Fact] @@ -100,7 +94,6 @@ public void TryParseBadNumbers() var checkParse = PhoneNumber.TryParse(number, out var phoneNumber); Assert.False(checkParse); - Assert.True(phoneNumber is not null); Assert.True(phoneNumber.DialedNumber == string.Empty); } } @@ -111,45 +104,38 @@ public void TryParseBenchmark() var checkParse = PhoneNumber.TryParse("ppboinine", out var phoneNumber); Assert.False(checkParse); - Assert.NotNull(phoneNumber); Assert.True(phoneNumber.DialedNumber == string.Empty); checkParse = PhoneNumber.TryParse("2sma", out phoneNumber); Assert.False(checkParse); - Assert.NotNull(phoneNumber); Assert.True(phoneNumber.DialedNumber == string.Empty); checkParse = PhoneNumber.TryParse("1 (111) 111-1111", out phoneNumber); Assert.False(checkParse); - Assert.NotNull(phoneNumber); Assert.True(phoneNumber.DialedNumber == string.Empty); checkParse = PhoneNumber.TryParse("15555551212", out phoneNumber); Assert.False(checkParse); - Assert.NotNull(phoneNumber); Assert.True(phoneNumber.DialedNumber == string.Empty); checkParse = PhoneNumber.TryParse("12068589310", out phoneNumber); Assert.True(checkParse); - Assert.NotNull(phoneNumber); Assert.True(phoneNumber.DialedNumber != string.Empty); // Short code checkParse = PhoneNumber.TryParse("FUNNY", out phoneNumber); Assert.True(checkParse); - Assert.NotNull(phoneNumber); Assert.True(phoneNumber.DialedNumber != string.Empty); // Short code with incorrect 1 prefix. checkParse = PhoneNumber.TryParse("140404", out phoneNumber); Assert.True(checkParse); - Assert.NotNull(phoneNumber); Assert.True(phoneNumber.DialedNumber != string.Empty); } @@ -160,8 +146,7 @@ public void TryParseNANPAContactNumbers() { var checkParse = PhoneNumber.TryParse(number, out PhoneNumber phoneNumber); Assert.True(checkParse); - Assert.True(phoneNumber is not null); - Assert.False(string.IsNullOrWhiteSpace(phoneNumber?.DialedNumber)); + Assert.False(string.IsNullOrWhiteSpace(phoneNumber.DialedNumber)); } } @@ -201,45 +186,48 @@ public void InvalidNumbers() [Fact] public void ValidNPAs() { - Assert.True(AreaCode.All.Any()); - foreach (var npa in AreaCode.All) + Assert.NotEmpty(AreaCodes.All); + foreach (var npa in AreaCodes.All) { + var local = npa; Assert.True(npa.ToString().IsValidNPA()); - Assert.True(AreaCode.ValidNPA(npa)); - Assert.True(AreaCode.ValidNPA(npa.ToString())); + Assert.True(AreaCode.ValidNPA(ref local)); + Assert.True(AreaCode.ValidNPA(local.ToString())); } } [Fact] public void ValidTollfreeNPAs() { - Assert.True(AreaCode.TollFree.Any()); - foreach (var npa in AreaCode.TollFree) + Assert.NotEmpty(AreaCodes.TollFree); + foreach (var npa in AreaCodes.TollFree) { + var local = npa; Assert.True(npa.ToString().IsValidNPA()); Assert.True(npa.ToString().IsTollfree()); Assert.True(npa.ToString().IsNonGeographic()); - Assert.True(AreaCode.ValidNPA(npa)); - Assert.True(AreaCode.ValidTollfree(npa)); - Assert.True(AreaCode.ValidNonGeographic(npa)); - Assert.True(AreaCode.ValidNPA(npa.ToString())); - Assert.True(AreaCode.ValidTollfree(npa.ToString())); - Assert.True(AreaCode.ValidNonGeographic(npa.ToString())); + Assert.True(AreaCode.ValidNPA(ref local)); + Assert.True(AreaCode.ValidTollfree(ref local)); + Assert.True(AreaCode.ValidNonGeographic(ref local)); + Assert.True(AreaCode.ValidNPA(local.ToString())); + Assert.True(AreaCode.ValidTollfree(local.ToString())); + Assert.True(AreaCode.ValidNonGeographic(local.ToString())); } } [Fact] public void ValidNonGeographicNPAs() { - Assert.True(AreaCode.NonGeographic.Any()); - foreach (var npa in AreaCode.NonGeographic) + Assert.NotEmpty(AreaCodes.NonGeographic); + foreach (var npa in AreaCodes.NonGeographic) { + var local = npa; Assert.True(npa.ToString().IsValidNPA()); Assert.True(npa.ToString().IsNonGeographic()); - Assert.True(AreaCode.ValidNPA(npa)); - Assert.True(AreaCode.ValidNonGeographic(npa)); - Assert.True(AreaCode.ValidNPA(npa.ToString())); - Assert.True(AreaCode.ValidNonGeographic(npa.ToString())); + Assert.True(AreaCode.ValidNPA(ref local)); + Assert.True(AreaCode.ValidNonGeographic(ref local)); + Assert.True(AreaCode.ValidNPA(local.ToString())); + Assert.True(AreaCode.ValidNonGeographic(local.ToString())); } } @@ -304,24 +292,21 @@ public void UseCases() Assert.True(checkValidNumber); var checkParse = PhoneNumber.TryParse(input, out PhoneNumber phoneNumber); Assert.True(checkParse); - Assert.True(phoneNumber is not null); - if (phoneNumber is not null) - { - Assert.True(phoneNumber.DialedNumber?.IsValidPhoneNumber() ?? false); - Assert.True(AreaCode.ValidNPA(phoneNumber.NPA)); - Assert.True(AreaCode.ValidNXX(phoneNumber.NXX)); - Assert.True(AreaCode.ValidXXXX(phoneNumber.XXXX)); - Assert.True(AreaCode.ValidTollfree(phoneNumber.NPA)); - } + Assert.True(phoneNumber.DialedNumber?.IsValidPhoneNumber() ?? false); + Assert.True(AreaCode.ValidNPA(phoneNumber.NPA)); + Assert.True(AreaCode.ValidNXX(phoneNumber.NXX)); + Assert.True(AreaCode.ValidXXXX(phoneNumber.XXXX)); + Assert.True(AreaCode.ValidTollfree(phoneNumber.NPA)); + } [Fact] public void CodesByState() { - Assert.True(AreaCode.States.Any()); + Assert.NotEmpty(AreaCode.States); foreach (var state in AreaCode.States) { - if (state.AreaCodes is not null && state.AreaCodes.Any()) + if (state.AreaCodes is not null && state.AreaCodes.Length != 0) { foreach (var code in state.AreaCodes) { @@ -333,124 +318,128 @@ public void CodesByState() } } - [Fact] - public async void VerifyGeographicCodesFromNANPA() - { - using var client = new HttpClient(); - client.DefaultRequestHeaders.Clear(); - string? s = null; - var content = await client.GetAsync("https://nationalnanpa.com/enas/geoAreaCodeNumberReport.do"); - using (var sr = new StreamReader(await content.Content.ReadAsStreamAsync(), Encoding.GetEncoding("iso-8859-1"))) - { - s = sr.ReadToEnd(); - } - var pattern = "\\d\\d\\d"; - var rgx = new Regex(pattern); - - output.WriteLine(rgx.Matches(s).Count.ToString()); - - foreach (Match match in rgx.Matches(s).Cast()) - { - var checkParse = int.TryParse(match.ValueSpan, out int npa); - if (checkParse) - { - if (npa > 200 && npa < 999) - { - Assert.True(AreaCode.AllFlatLookup[npa], $"NPA {npa} is {AreaCode.AllFlatLookup[npa]} in the lookup."); - } - } - } - } - - [Fact] - public async void VerifyNonGeographicCodesFromNANPA() - { - using var client = new HttpClient(); - client.DefaultRequestHeaders.Clear(); - string? s = null; - var content = await client.GetAsync("https://www.nationalnanpa.com/enas/nonGeoNpaServiceReport.do"); - using (var sr = new StreamReader(await content.Content.ReadAsStreamAsync(), Encoding.GetEncoding("iso-8859-1"))) - { - s = sr.ReadToEnd(); - } - var pattern = "\\d\\d\\d"; - var rgx = new Regex(pattern); - - output.WriteLine(rgx.Matches(s).Count.ToString()); - - foreach (Match match in rgx.Matches(s).Cast()) - { - var checkParse = int.TryParse(match.ValueSpan, out int npa); - if (checkParse) - { - if (npa > 499 && npa < 901) - { - Assert.True(AreaCode.NonGeographicFlatLookup[npa], $"NPA {npa} is {AreaCode.NonGeographicFlatLookup[npa]} in the lookup."); - } - } - } - } - - [Fact] - public async void VerifyTollfreeCodesFromNANPA() - { - using var client = new HttpClient(); - client.DefaultRequestHeaders.Clear(); - string? s = null; - var content = await client.GetAsync("https://www.nationalnanpa.com/enas/nonGeoNpaServiceReport.do"); - using (var sr = new StreamReader(await content.Content.ReadAsStreamAsync(), Encoding.GetEncoding("iso-8859-1"))) - { - s = sr.ReadToEnd(); - } - var pattern = "\\d\\d\\d"; - var rgx = new Regex(pattern); - - output.WriteLine(rgx.Matches(s).Count.ToString()); - - foreach (Match match in rgx.Matches(s).Cast()) - { - var checkParse = int.TryParse(match.ValueSpan, out int npa); - if (checkParse) - { - if (npa > 799 && npa < 889) - { - output.WriteLine(npa.ToString()); - Assert.True(AreaCode.TollFreeFlatLookup[npa], $"NPA {npa} is {AreaCode.TollFreeFlatLookup[npa]} not in the lookup."); - } - } - } - } - - [Fact] - public async void VerifyAllAreaCodesFromNANPA() - { - using var client = new HttpClient(); - client.DefaultRequestHeaders.Clear(); - string? s = null; - var content = await client.GetAsync("https://www.nationalnanpa.com/enas/nonGeoNpaServiceReport.do"); - using (var sr = new StreamReader(await content.Content.ReadAsStreamAsync(), Encoding.GetEncoding("iso-8859-1"))) - { - s = sr.ReadToEnd(); - } - var pattern = "\\d\\d\\d"; - var rgx = new Regex(pattern); - - output.WriteLine(rgx.Matches(s).Count.ToString()); - - foreach (Match match in rgx.Matches(s).Cast()) - { - var checkParse = int.TryParse(match.ValueSpan, out int npa); - if (checkParse) - { - if (npa > 499 && npa < 999) - { - output.WriteLine(npa.ToString()); - Assert.True(AreaCode.AllFlatLookup[npa], $"NPA {npa} is {AreaCode.AllFlatLookup[npa]} not in the lookup."); - } - } - } - - } + // Disabled because NANPA removed this report from their website. + //[Fact] + //public async void VerifyGeographicCodesFromNANPA() + //{ + // using var client = new HttpClient(); + // client.DefaultRequestHeaders.Clear(); + // string? s = null; + // var content = await client.GetAsync("https://nationalnanpa.com/enas/geoAreaCodeNumberReport.do"); + // using (var sr = new StreamReader(await content.Content.ReadAsStreamAsync(), Encoding.GetEncoding("iso-8859-1"))) + // { + // s = sr.ReadToEnd(); + // } + // var pattern = "\\d\\d\\d"; + // var rgx = new Regex(pattern); + + // output.WriteLine(rgx.Matches(s).Count.ToString()); + + // foreach (Match match in rgx.Matches(s).Cast()) + // { + // var checkParse = int.TryParse(match.ValueSpan, out int npa); + // if (checkParse) + // { + // if (npa > 200 && npa < 999) + // { + // Assert.True(AreaCode.AllFlatLookup[npa], $"NPA {npa} is {AreaCode.AllFlatLookup[npa]} in the lookup."); + // } + // } + // } + //} + + // Disabled because NANPA removed this report from their website. + //[Fact] + //public async void VerifyNonGeographicCodesFromNANPA() + //{ + // using var client = new HttpClient(); + // client.DefaultRequestHeaders.Clear(); + // string? s = null; + // var content = await client.GetAsync("https://www.nationalnanpa.com/enas/nonGeoNpaServiceReport.do"); + // using (var sr = new StreamReader(await content.Content.ReadAsStreamAsync(), Encoding.GetEncoding("iso-8859-1"))) + // { + // s = sr.ReadToEnd(); + // } + // var pattern = "\\d\\d\\d"; + // var rgx = new Regex(pattern); + + // output.WriteLine(rgx.Matches(s).Count.ToString()); + + // foreach (Match match in rgx.Matches(s).Cast()) + // { + // var checkParse = int.TryParse(match.ValueSpan, out int npa); + // if (checkParse) + // { + // if (npa > 499 && npa < 901) + // { + // Assert.True(AreaCode.NonGeographicFlatLookup[npa], $"NPA {npa} is {AreaCode.NonGeographicFlatLookup[npa]} in the lookup."); + // } + // } + // } + //} + + // Disabled because NANPA removed this report from their website. + //[Fact] + //public async void VerifyTollfreeCodesFromNANPA() + //{ + // using var client = new HttpClient(); + // client.DefaultRequestHeaders.Clear(); + // string? s = null; + // var content = await client.GetAsync("https://www.nationalnanpa.com/enas/nonGeoNpaServiceReport.do"); + // using (var sr = new StreamReader(await content.Content.ReadAsStreamAsync(), Encoding.GetEncoding("iso-8859-1"))) + // { + // s = sr.ReadToEnd(); + // } + // var pattern = "\\d\\d\\d"; + // var rgx = new Regex(pattern); + + // output.WriteLine(rgx.Matches(s).Count.ToString()); + + // foreach (Match match in rgx.Matches(s).Cast()) + // { + // var checkParse = int.TryParse(match.ValueSpan, out int npa); + // if (checkParse) + // { + // if (npa > 799 && npa < 889) + // { + // output.WriteLine(npa.ToString()); + // Assert.True(AreaCode.TollFreeFlatLookup[npa], $"NPA {npa} is {AreaCode.TollFreeFlatLookup[npa]} not in the lookup."); + // } + // } + // } + //} + + // Disabled because NANPA removed this report from their website. + //[Fact] + //public async void VerifyAllAreaCodesFromNANPA() + //{ + // using var client = new HttpClient(); + // client.DefaultRequestHeaders.Clear(); + // string? s = null; + // var content = await client.GetAsync("https://www.nationalnanpa.com/enas/nonGeoNpaServiceReport.do"); + // using (var sr = new StreamReader(await content.Content.ReadAsStreamAsync(), Encoding.GetEncoding("iso-8859-1"))) + // { + // s = sr.ReadToEnd(); + // } + // var pattern = "\\d\\d\\d"; + // var rgx = new Regex(pattern); + + // output.WriteLine(rgx.Matches(s).Count.ToString()); + + // foreach (Match match in rgx.Matches(s).Cast()) + // { + // var checkParse = int.TryParse(match.ValueSpan, out int npa); + // if (checkParse) + // { + // if (npa > 499 && npa < 999) + // { + // output.WriteLine(npa.ToString()); + // Assert.True(AreaCode.AllFlatLookup[npa], $"NPA {npa} is {AreaCode.AllFlatLookup[npa]} not in the lookup."); + // } + // } + // } + + //} // This doesn't work because the CNA website is written using VueJS. // The NPA's aren't rendered unless the JS in the initial response is executed, which doesn't happen because this is not a browser. diff --git a/PhoneNumbersNA/AreaCode.cs b/PhoneNumbersNA/AreaCode.cs index dc112e3..528187a 100644 --- a/PhoneNumbersNA/AreaCode.cs +++ b/PhoneNumbersNA/AreaCode.cs @@ -8,32 +8,20 @@ namespace PhoneNumbersNA { public static class Parse { - public static IEnumerable AsPhoneNumbers(string str) - { - return AreaCode.ExtractPhoneNumbers(str.AsSpan()); - } - public static IEnumerable AsPhoneNumbers(ReadOnlySpan str) - { - return AreaCode.ExtractPhoneNumbers(str); - } - public static IEnumerable AsDialedNumbers(string str) - { - return AreaCode.ExtractDialedNumbers(str.AsSpan()); - } - public static IEnumerable AsDialedNumbers(ReadOnlySpan str) - { - return AreaCode.ExtractDialedNumbers(str); - } + public static ReadOnlySpan AsPhoneNumbers(string str) => AreaCode.ExtractPhoneNumbers(str.AsSpan()); + public static ReadOnlySpan AsPhoneNumbers(ReadOnlySpan str) => AreaCode.ExtractPhoneNumbers(str); + public static ReadOnlySpan AsDialedNumbers(string str) => AreaCode.ExtractDialedNumbers(str.AsSpan()); + public static ReadOnlySpan AsDialedNumbers(ReadOnlySpan str) => AreaCode.ExtractDialedNumbers(str); } - public static class AreaCode + public readonly ref struct AreaCodes { /// /// All in service NANPA NPAs (Area Codes). /// https://nationalnanpa.com/enas/geoAreaCodeNumberReport.do /// - public static readonly int[] All = new int[] - { + public static readonly int[] All = + [ 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 223, 224, 225, 226, 227, 228, 229, 231, 234, 235, 236, 239, 240, 242, 246, 248, 249, 250, 251, 252, 253, 254, 256, 260, 262, 263, 264, 267, 268, 269, 270, 272, 274, 276, 278, 279, 281, 283, 284, 289, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 323, 324, 325, 326, 327, 329, 330, 331, 332, 334, 336, 337, 339, 340, 341, 343, 345, 346, 347, 350, 351, 352, 353, 354, 360, 361, 363, 364, 365, 367, 368, 369, 380, 381, 382, 385, 386, 387, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 423, 424, 425, 428, 430, 431, 432, 434, 435, 436, 437, 438, 440, 441, 442, 443, 445, 447, 448, 450, 456, 458, 463, 464, 468, 469, 470, 472, 473, 474, 475, 478, 479, 480, 484, @@ -42,36 +30,36 @@ public static class AreaCode 700, 701, 702, 703, 704, 705, 706, 707, 708, 709, 710, 711, 712, 713, 714, 715, 716, 717, 718, 719, 720, 721, 724, 725, 726, 727, 728, 730, 731, 732, 734, 737, 740, 742, 743, 747, 753, 754, 757, 758, 760, 762, 763, 765, 767, 769, 770, 771, 772, 773, 774, 775, 778, 779, 780, 781, 782, 784, 785, 786, 787, 800, 801, 802, 803, 804, 805, 806, 807, 808, 809, 810, 811, 812, 813, 814, 815, 816, 817, 818, 819, 820, 825, 826, 828, 829, 830, 831, 832, 833, 835, 838, 839, 840, 843, 844, 845, 847, 848, 849, 850, 854, 855, 856, 857, 858, 859, 860, 861, 862, 863, 864, 865, 866, 867, 868, 869, 870, 872, 873, 876, 877, 878, 879, 888, 889, 900, 901, 902, 903, 904, 905, 906, 907, 908, 909, 910, 912, 913, 914, 915, 916, 917, 918, 919, 920, 925, 927, 928, 929, 930, 931, 934, 935, 936, 937, 938, 939, 940, 941, 943, 945, 947, 948, 949, 951, 952, 954, 956, 959, 970, 971, 972, 973, 975, 978, 979, 980, 983, 984, 985, 986, 989 - }; + ]; /// /// All in service NANPA Tollfree NPAs (Area Codes). /// https://nationalnanpa.com/enas/nonGeoNpaServiceReport.do /// - public static readonly int[] TollFree = new int[] - { + public static readonly int[] TollFree = + [ 800, 833, 844, 855, 866, 877, 888 - }; + ]; /// /// All in service NANPA Non-geographics NPAs (Area Codes) including tollfree numbers. /// https://nationalnanpa.com/enas/nonGeoNpaServiceReport.do /// - public static readonly int[] NonGeographic = new int[] - { + public static readonly int[] NonGeographic = + [ 500, 521, 522, 523, 524, 525, 526, 527, 528, 529, 533, 544, 566, 577, 588, 600, 622, 633, 700, 710, 800, 833, 844, 855, 866, 877, 888, 900 - }; + ]; /// /// All in service NPAs maintained by the CNA ( Canadian Numbering Administrator ). /// https://cnac.ca/co_codes/co_code_lookup.htm /// - public static readonly int[] Canadian = new int[] - { + public static readonly int[] Canadian = + [ 204, 226, 236, 249, 250, 263, 289, 306, 343, 354, 365, 367, 368, 382, 403, 416, 418, 428, 431, 437, 438, 450, 468, 474, @@ -80,14 +68,14 @@ public static class AreaCode 705, 709, 742, 753, 778, 780, 782, 807, 819, 825, 867, 873, 879, 902, 905 - }; + ]; /// /// All in service NANPA Geographics NPAs (Area Codes) not directly in the United States or Canada. Includes sovereign nations and US territories. /// https://nationalnanpa.com/area_code_maps/area_code_maps_Country_Territory.html /// - public static readonly int[] CountryOrTerritory = new int[] - { + public static readonly int[] CountryOrTerritory = + [ 242, 246, 264, 268, 284, 340, 345, 441, 473, @@ -95,8 +83,11 @@ public static class AreaCode 721, 758, 767, 784, 787, 809, 829, 849, 868, 869, 876, 939 - }; + ]; + } + public static class AreaCode + { // Rather than directly looping over the area code arrays or storing them in a dictionary we can use a simple array of boolean as lookup. // Where the index of the array is the area code as an int and the boolean returned discribes if its valid or not. // Using the PhoneNumbersNA.Benchmark console app we verified that this flat lookup is faster than a simple loop or dictionary. @@ -106,14 +97,14 @@ public static class AreaCode public static readonly bool[] CanadianFlatLookup = CanadianFlat(); public static readonly bool[] CountryOrTerritoryFlatLookup = CountryOrTerritoryFlat(); - public static Dictionary GetAsDictionary() => All.ToDictionary(x => x, y => y); + public static Dictionary GetAsDictionary() => AreaCodes.All.ToDictionary(static x => x, static y => y); // Generator methods for the flat lookups. private static bool[] AllFlat() { bool[] array = new bool[999]; - foreach (int code in All) + foreach (int code in AreaCodes.All) { array[code] = true; } @@ -124,7 +115,7 @@ private static bool[] TollFreeFlat() { bool[] array = new bool[999]; - foreach (int code in TollFree) + foreach (int code in AreaCodes.TollFree) { array[code] = true; } @@ -135,7 +126,7 @@ private static bool[] NonGeographicFlat() { bool[] array = new bool[999]; - foreach (int code in NonGeographic) + foreach (int code in AreaCodes.NonGeographic) { array[code] = true; } @@ -146,7 +137,7 @@ private static bool[] CanadianFlat() { bool[] array = new bool[999]; - foreach (int code in Canadian) + foreach (int code in AreaCodes.Canadian) { array[code] = true; } @@ -157,485 +148,330 @@ private static bool[] CountryOrTerritoryFlat() { bool[] array = new bool[999]; - foreach (int code in CountryOrTerritory) + foreach (int code in AreaCodes.CountryOrTerritory) { array[code] = true; } return array; } - public class AreaCodesByState - { - public string State { get; set; } = string.Empty; - public string StateShort { get; set; } = string.Empty; - public int[] AreaCodes { get; set; } = Array.Empty(); - } + public readonly record struct AreaCodesByState(ref readonly string State, ref readonly string StateShort, ref readonly int[] AreaCodes); /// /// NANPA AreaCodes by Location https://nationalnanpa.com/enas/geoAreaCodeNumberReport.do /// - public static readonly AreaCodesByState[] States = new AreaCodesByState[] - { + public static readonly AreaCodesByState[] States = + [ new AreaCodesByState { State = "Alabama", StateShort = "AL", - AreaCodes = new int[] - { - 205, 251, 256, 334, 938 - } + AreaCodes = [205, 251, 256, 334, 938] }, new AreaCodesByState { State = "Alaska", StateShort = "AK", - AreaCodes = new int[] - { - 907 - } + AreaCodes = [907] }, new AreaCodesByState { State = "Arizona", StateShort = "AZ", - AreaCodes = new int[] - { - 480, 520, 602, 623, 928 - } + AreaCodes = [480, 520, 602, 623, 928] }, new AreaCodesByState { State = "Arkansas", StateShort = "AR", - AreaCodes = new int[] - { - 479, 501, 870 - } + AreaCodes = [479, 501, 870] }, new AreaCodesByState { State = "California", StateShort = "CA", - AreaCodes = new int[] - { - 209, 213, 279, 310, 323, 408, 415, 424, 442, 510, 530, 559, 562, 619, 626, 628, 650, 657, 661, 669, 707, 714, 747, 760, 805, 818, 820, 831, 858, 909, 916, 925, 949, 951 - } + AreaCodes = [209, 213, 279, 310, 323, 408, 415, 424, 442, 510, 530, 559, 562, 619, 626, 628, 650, 657, 661, 669, 707, 714, 747, 760, 805, 818, 820, 831, 858, 909, 916, 925, 949, 951] }, new AreaCodesByState { State = "Colorado", StateShort = "CO", - AreaCodes = new int[] - { - 303, 719, 720, 970 - } + AreaCodes = [303, 719, 720, 970] }, new AreaCodesByState { State = "Connecticut", StateShort = "CT", - AreaCodes = new int[] - { - 203, 475, 860, 959 - } + AreaCodes = [203, 475, 860, 959] }, new AreaCodesByState { State = "Delaware", StateShort = "DE", - AreaCodes = new int[] - { - 302 - } + AreaCodes = [302] }, new AreaCodesByState { State = "Florida", StateShort = "FL", - AreaCodes = new int[] - { - 239, 305, 321, 352, 386, 407, 561, 727, 754, 772, 786, 813, 850, 863, 904, 941, 954 - } + AreaCodes = [239, 305, 321, 352, 386, 407, 561, 727, 754, 772, 786, 813, 850, 863, 904, 941, 954] }, new AreaCodesByState { State = "Georgia", StateShort = "GA", - AreaCodes = new int[] - { - 229, 404, 470, 478, 678, 706, 762, 770, 912, 943 - } + AreaCodes = [229, 404, 470, 478, 678, 706, 762, 770, 912, 943] }, new AreaCodesByState { State = "Hawaii", StateShort = "HI", - AreaCodes = new int[] - { - 808 - } + AreaCodes = [808] }, new AreaCodesByState { State = "Idaho", StateShort = "ID", - AreaCodes = new int[] - { - 208, 986 - } + AreaCodes = [208, 986] }, new AreaCodesByState { State = "Illinois", StateShort = "IL", - AreaCodes = new int[] - { - 217, 224, 309, 312, 331, 618, 630, 708, 773, 779, 815, 847, 872 - } + AreaCodes = [217, 224, 309, 312, 331, 618, 630, 708, 773, 779, 815, 847, 872] }, new AreaCodesByState { State = "Indiana", StateShort = "IN", - AreaCodes = new int[] - { - 219, 260, 317, 463, 574, 765, 812, 930 - } + AreaCodes = [219, 260, 317, 463, 574, 765, 812, 930] }, new AreaCodesByState { State = "Iowa", StateShort = "IA", - AreaCodes = new int[] - { - 319, 515, 563, 641, 712 - } + AreaCodes = [319, 515, 563, 641, 712] }, new AreaCodesByState { State = "Kansas", StateShort = "KS", - AreaCodes = new int[] - { - 316, 620, 785, 913 - } + AreaCodes = [316, 620, 785, 913] }, new AreaCodesByState { State = "Kentucky", StateShort = "KY", - AreaCodes = new int[] - { - 270, 364, 502, 606, 859 - } + AreaCodes = [270, 364, 502, 606, 859] }, new AreaCodesByState { State = "Louisiana", StateShort = "LA", - AreaCodes = new int[] - { - 225, 318, 337, 504, 985 - } + AreaCodes = [225, 318, 337, 504, 985] }, new AreaCodesByState { State = "Maine", StateShort = "ME", - AreaCodes = new int[] - { - 207 - } + AreaCodes = [207] }, new AreaCodesByState { State = "Maryland", StateShort = "MD", - AreaCodes = new int[] - { - 240, 301, 410, 443, 667 - } + AreaCodes = [240, 301, 410, 443, 667] }, new AreaCodesByState { State = "Massachusetts", StateShort = "MA", - AreaCodes = new int[] - { - 339, 351, 413, 508, 617, 774, 781, 857, 978 - } + AreaCodes = [339, 351, 413, 508, 617, 774, 781, 857, 978] }, new AreaCodesByState { State = "Michigan", StateShort = "MI", - AreaCodes = new int[] - { - 231, 248, 269, 313, 517, 586, 616, 734, 810, 906, 947, 989 - } + AreaCodes = [231, 248, 269, 313, 517, 586, 616, 734, 810, 906, 947, 989] }, new AreaCodesByState { State = "Minnesota", StateShort = "MN", - AreaCodes = new int[] - { - 218, 320, 507, 612, 651, 763, 952 - } + AreaCodes = [218, 320, 507, 612, 651, 763, 952] }, new AreaCodesByState { State = "Mississippi", StateShort = "MS", - AreaCodes = new int[] - { - 228, 601, 662, 769 - } + AreaCodes = [228, 601, 662, 769] }, new AreaCodesByState { State = "Missouri", StateShort = "MO", - AreaCodes = new int[] - { - 314, 417, 573, 636, 660, 816 - } + AreaCodes = [314, 417, 573, 636, 660, 816] }, new AreaCodesByState { State = "Montana", StateShort = "MT", - AreaCodes = new int[] - { - 406 - } + AreaCodes = [406] }, new AreaCodesByState { State = "Nebraska", StateShort = "NE", - AreaCodes = new int[] - { - 308, 402, 531 - } + AreaCodes = [308, 402, 531] }, new AreaCodesByState { State = "Nevada", StateShort = "NV", - AreaCodes = new int[] - { - 702, 725, 775 - } + AreaCodes = [702, 725, 775] }, new AreaCodesByState { State = "New Hampshire", StateShort = "NH", - AreaCodes = new int[] - { - 603 - } + AreaCodes = [603] }, new AreaCodesByState { State = "New Jersey", StateShort = "NJ", - AreaCodes = new int[] - { - 201, 551, 609, 640, 732, 848, 856, 862, 908, 973 - } + AreaCodes = [201, 551, 609, 640, 732, 848, 856, 862, 908, 973] }, new AreaCodesByState { State = "New Mexico", StateShort = "NM", - AreaCodes = new int[] - { - 505, 575 - } + AreaCodes = [505, 575] }, new AreaCodesByState { State = "New York", StateShort = "NY", - AreaCodes = new int[] - { - 212, 315, 332, 347, 516, 518, 585, 607, 631, 646, 680, 716, 718, 838, 845, 914, 917, 929, 934 - } + AreaCodes = [212, 315, 332, 347, 516, 518, 585, 607, 631, 646, 680, 716, 718, 838, 845, 914, 917, 929, 934] }, new AreaCodesByState { State = "North Carolina", StateShort = "NC", - AreaCodes = new int[] - { - 252, 336, 704, 743, 828, 910, 919, 980, 984 - } + AreaCodes = [252, 336, 704, 743, 828, 910, 919, 980, 984] }, new AreaCodesByState { State = "Ohio", StateShort = "OH", - AreaCodes = new int[] - { - 216, 220, 234, 330, 380, 419, 440, 513, 567, 614, 740, 937 - } + AreaCodes = [216, 220, 234, 330, 380, 419, 440, 513, 567, 614, 740, 937] }, new AreaCodesByState { State = "Oklahoma", StateShort = "OK", - AreaCodes = new int[] - { - 405, 539, 580, 918 - } + AreaCodes = [405, 539, 580, 918] }, new AreaCodesByState { State = "Oregon", StateShort = "OR", - AreaCodes = new int[] - { - 458, 503, 541, 971 - } + AreaCodes = [458, 503, 541, 971] }, new AreaCodesByState { State = "Pennsylvania", StateShort = "PA", - AreaCodes = new int[] - { - 215, 223, 267, 272, 412, 445, 484, 570, 610, 717, 724, 814, 878 - } + AreaCodes = [215, 223, 267, 272, 412, 445, 484, 570, 610, 717, 724, 814, 878] }, new AreaCodesByState { State = "Rhode Island", StateShort = "RI", - AreaCodes = new int[] - { - 401 - } + AreaCodes = [401] }, new AreaCodesByState { State = "South Carolina", StateShort = "SC", - AreaCodes = new int[] - { - 803, 843, 854, 864 - } + AreaCodes = [803, 843, 854, 864] }, new AreaCodesByState { State = "South Dakota", StateShort = "SD", - AreaCodes = new int[] - { - 605 - } + AreaCodes = [605] }, new AreaCodesByState { State = "Tennessee", StateShort = "TN", - AreaCodes = new int[] - { - 423, 615, 629, 731, 865, 901, 931 - } + AreaCodes = [423, 615, 629, 731, 865, 901, 931] }, new AreaCodesByState { State = "Texas", StateShort = "TX", - AreaCodes = new int[] - { - 210, 214, 254, 281, 325, 346, 361, 409, 430, 432, 469, 512, 682, 713, 726, 737, 806, 817, 830, 832, 903, 915, 936, 940, 956, 972, 979 - } + AreaCodes = [210, 214, 254, 281, 325, 346, 361, 409, 430, 432, 469, 512, 682, 713, 726, 737, 806, 817, 830, 832, 903, 915, 936, 940, 956, 972, 979] }, new AreaCodesByState { State = "Utah", StateShort = "UT", - AreaCodes = new int[] - { - 385, 435, 801 - } + AreaCodes = [385, 435, 801] }, new AreaCodesByState { State = "Vermont", StateShort = "VT", - AreaCodes = new int[] - { - 802 - } + AreaCodes = [802] }, new AreaCodesByState { State = "Virginia", StateShort = "VA", - AreaCodes = new int[] - { - 276, 434, 540, 571, 703, 757, 804 - } + AreaCodes = [276, 434, 540, 571, 703, 757, 804] }, new AreaCodesByState { State = "Washington", StateShort = "WA", - AreaCodes = new int[] - { - 206, 253, 360, 425, 509, 564 - } + AreaCodes = [206, 253, 360, 425, 509, 564] }, new AreaCodesByState { State = "Washington, DC", StateShort = "DC", - AreaCodes = new int[] - { - 202 - } + AreaCodes = [202] }, new AreaCodesByState { State = "West Virginia", StateShort = "WV", - AreaCodes = new int[] - { - 304, 681 - } + AreaCodes = [304, 681] }, new AreaCodesByState { State = "Wisconsin", StateShort = "WI", - AreaCodes = new int[] - { - 262, 414, 534, 608, 715, 920 - } + AreaCodes = [262, 414, 534, 608, 715, 920] }, new AreaCodesByState { State = "Wyoming", StateShort = "WY", - AreaCodes = new int[] - { - 307 - } + AreaCodes = [307] } - }; + ]; /// /// Check if an NPA (Area Code) is in service and in Canada. /// /// /// - public static bool ValidCanadian(int npa) + public static ref readonly bool ValidCanadian(ref int npa) { - return CanadianFlatLookup[npa]; + return ref CanadianFlatLookup[npa]; } /// @@ -651,7 +487,7 @@ public static bool ValidCanadian(string npa) if (checkParse) { - return ValidCanadian(canadian); + return ValidCanadian(ref canadian); } } return false; @@ -662,7 +498,7 @@ public static bool ValidCanadian(string npa) /// /// /// - public static bool ValidCanadian(ReadOnlySpan npa) + public static bool ValidCanadian(ref ReadOnlySpan npa) { if (npa.Length is 3) { @@ -670,7 +506,7 @@ public static bool ValidCanadian(ReadOnlySpan npa) if (checkParse) { - return ValidCanadian(canadian); + return ValidCanadian(ref canadian); } } return false; @@ -687,10 +523,11 @@ public static bool IsCanadian(this string str) if (!string.IsNullOrWhiteSpace(str)) { + ReadOnlySpan valid = number[..3]; return str.Length switch { - 10 => ValidCanadian(number[..3]), - _ => ValidCanadian(number), + 10 => ValidCanadian(ref valid), + _ => false, }; } return false; @@ -701,7 +538,7 @@ public static bool IsCanadian(this string str) /// /// /// - public static bool ValidCountryOrTerritory(int npa) + public static bool ValidCountryOrTerritory(ref int npa) { return CountryOrTerritoryFlatLookup[npa]; } @@ -719,7 +556,7 @@ public static bool ValidCountryOrTerritory(string npa) if (checkParse) { - return ValidCountryOrTerritory(country); + return ValidCountryOrTerritory(ref country); } } return false; @@ -730,7 +567,7 @@ public static bool ValidCountryOrTerritory(string npa) /// /// /// - public static bool ValidCountryOrTerritory(ReadOnlySpan npa) + public static bool ValidCountryOrTerritory(ref ReadOnlySpan npa) { if (npa.Length is 3) { @@ -738,7 +575,7 @@ public static bool ValidCountryOrTerritory(ReadOnlySpan npa) if (checkParse) { - return ValidCountryOrTerritory(country); + return ValidCountryOrTerritory(ref country); } } return false; @@ -755,10 +592,11 @@ public static bool IsCountryOrTerritory(this string str) if (!string.IsNullOrWhiteSpace(str)) { + ReadOnlySpan valid = number[..3]; return str.Length switch { - 10 => ValidCountryOrTerritory(number[..3]), - _ => ValidCountryOrTerritory(number), + 10 => ValidCountryOrTerritory(ref valid), + _ => ValidCountryOrTerritory(ref number), }; } return false; @@ -769,11 +607,16 @@ public static bool IsCountryOrTerritory(this string str) /// /// /// - public static bool ValidNonGeographic(int npa) + public static bool ValidNonGeographic(ref int npa) { return NonGeographicFlatLookup[npa]; } + public static bool ValidNonGeographic(int npa) + { + return ValidNonGeographic(ref npa); + } + /// /// Check if a string is an NPA (Area Code), is in service, and non geographic. /// @@ -787,7 +630,7 @@ public static bool ValidNonGeographic(string npa) if (checkParse) { - return ValidNonGeographic(nongeo); + return ValidNonGeographic(ref nongeo); } } return false; @@ -798,7 +641,7 @@ public static bool ValidNonGeographic(string npa) /// /// /// - public static bool ValidNonGeographic(ReadOnlySpan npa) + public static bool ValidNonGeographic(ref ReadOnlySpan npa) { if (npa.Length is 3) { @@ -806,7 +649,7 @@ public static bool ValidNonGeographic(ReadOnlySpan npa) if (checkParse) { - return ValidNonGeographic(nongeo); + return ValidNonGeographic(ref nongeo); } } return false; @@ -823,10 +666,11 @@ public static bool IsNonGeographic(this string str) if (!string.IsNullOrWhiteSpace(str)) { + ReadOnlySpan valid = number[..3]; return str.Length switch { - 10 => ValidNonGeographic(number[..3]), - _ => ValidNonGeographic(number), + 10 => ValidNonGeographic(ref valid), + _ => ValidNonGeographic(ref number), }; } return false; @@ -837,31 +681,42 @@ public static bool IsNonGeographic(this string str) /// /// /// - public static bool ValidTollfree(int npa) + public static bool ValidTollfree(ref int npa) { return TollFreeFlatLookup[npa]; } + public static bool ValidTollfree(int npa) + { + return ValidTollfree(ref npa); + } + /// /// Check if a string is an NPA (Area Code), is in service, and Tollfree. /// /// /// - public static bool ValidTollfree(string npa) + public static bool ValidTollfree(ref string npa) { if (!string.IsNullOrWhiteSpace(npa) && npa.Length is 3) { - return ValidTollfree(npa.AsSpan()); + ReadOnlySpan valid = npa.AsSpan(); + return ValidTollfree(ref valid); } return false; } + public static bool ValidTollfree(string npa) + { + return ValidTollfree(ref npa); + } + /// /// Check if a string is an NPA (Area Code), is in service, and Tollfree. /// /// /// - public static bool ValidTollfree(ReadOnlySpan npa) + public static bool ValidTollfree(ref ReadOnlySpan npa) { if (npa.Length is 3) { @@ -869,7 +724,7 @@ public static bool ValidTollfree(ReadOnlySpan npa) if (checkParse) { - return ValidTollfree(toll); + return ValidTollfree(ref toll); } } return false; @@ -886,10 +741,11 @@ public static bool IsTollfree(this string str) if (!string.IsNullOrWhiteSpace(str)) { + ReadOnlySpan valid = number[..3]; return str.Length switch { - 10 => ValidTollfree(number[..3]), - _ => ValidTollfree(number), + 10 => ValidTollfree(ref valid), + _ => ValidTollfree(ref number), }; } return false; @@ -901,29 +757,39 @@ public static bool IsTollfree(this string str) /// /// /// - public static bool ValidNPA(int npa) + public static bool ValidNPA(ref int npa) { return AllFlatLookup[npa]; } + public static bool ValidNPA(int npa) + { + return ValidNPA(ref npa); + } + /// /// Check if a string is a valid NANPA phone number NPA (Area Code) sequence in the format of "NXX". /// /// /// - public static bool ValidNPA(string npa) + public static bool ValidNPA(ref string npa) { if (!string.IsNullOrWhiteSpace(npa) && npa.Length is 3) { bool checkParse = int.TryParse(npa.AsSpan(), out int code); if (checkParse) { - return ValidNPA(code); + return ValidNPA(ref code); } } return false; } + public static bool ValidNPA(string npa) + { + return ValidNPA(ref npa); + } + /// /// Check if a string is a valid NANPA phone number NPA (Area Code) sequence in the format of "NXX". /// @@ -940,7 +806,7 @@ public static bool IsValidNPA(this string str) /// /// /// - public static bool ValidNXX(int nxx) + public static bool ValidNXX(ref int nxx) { if (nxx >= 200 && nxx <= 999) { @@ -949,24 +815,34 @@ public static bool ValidNXX(int nxx) return false; } + public static bool ValidNXX(int nxx) + { + return ValidNXX(ref nxx); + } + /// /// Validate a string-ly typed 3 digit nxx sequence in the format of "NXX". /// /// /// - public static bool ValidNXX(string nxx) + public static bool ValidNXX(ref string nxx) { if (!string.IsNullOrWhiteSpace(nxx) && nxx.Length is 3) { bool checkParse = int.TryParse(nxx.AsSpan(), out int office); if (checkParse) { - return ValidNXX(office); + return ValidNXX(ref office); } } return false; } + public static bool ValidNXX(string nxx) + { + return ValidNXX(ref nxx); + } + /// /// Check if a string is a valid NANPA phone number sequence in the format of "NXX". /// @@ -974,7 +850,7 @@ public static bool ValidNXX(string nxx) /// public static bool IsValidNXX(this string str) { - return ValidNXX(str); + return ValidNXX(ref str); } /// @@ -982,7 +858,7 @@ public static bool IsValidNXX(this string str) /// /// /// - public static bool ValidXXXX(int xxxx) + public static bool ValidXXXX(ref int xxxx) { if (xxxx >= 0 && xxxx <= 9999) { @@ -991,24 +867,34 @@ public static bool ValidXXXX(int xxxx) return false; } + public static bool ValidXXXX(int xxxx) + { + return ValidXXXX(ref xxxx); + } + /// /// Validate a string-ly typed 4 digit vanity sequence in the format of "XXXX". /// /// /// - public static bool ValidXXXX(string xxxx) + public static bool ValidXXXX(ref string xxxx) { if (!string.IsNullOrWhiteSpace(xxxx) && xxxx.Length is 4) { bool checkParse = int.TryParse(xxxx.AsSpan(), out int vanity); if (checkParse) { - return ValidXXXX(vanity); + return ValidXXXX(ref vanity); } } return false; } + public static bool ValidXXXX(string xxxx) + { + return ValidXXXX(ref xxxx); + } + /// /// Check if a string is a valid NANPA phone number vanity sequence in the format of "XXXX" /// @@ -1016,7 +902,7 @@ public static bool ValidXXXX(string xxxx) /// public static bool IsValidXXXX(this string str) { - return ValidXXXX(str); + return ValidXXXX(ref str); } /// @@ -1026,9 +912,14 @@ public static bool IsValidXXXX(this string str) /// /// /// + public static bool ValidPhoneNumber(ref int npa, ref int nxx, ref int xxxx) + { + return ValidNPA(ref npa) && ValidNXX(ref nxx) && ValidXXXX(ref xxxx); + } + public static bool ValidPhoneNumber(int npa, int nxx, int xxxx) { - return ValidNPA(npa) && ValidNXX(nxx) && ValidXXXX(xxxx); + return ValidPhoneNumber(ref npa, ref nxx, ref xxxx); } /// @@ -1060,7 +951,7 @@ public static bool ValidPhoneNumber(ReadOnlySpan dialedNumber) if (checkNpa && checkNxx && checkXxxx) { - return ValidPhoneNumber(npa, nxx, xxxx); + return ValidPhoneNumber(ref npa, ref nxx, ref xxxx); } } return false; @@ -1093,7 +984,7 @@ public static bool IsValidPhoneNumber(this ReadOnlySpan input) foreach (char letter in input) { // Allow digits, drop leading 1's as NANPA area codes start at 201. - if (char.IsDigit(letter) && (letter is not '1' || converted.Any())) + if (char.IsDigit(letter) && (letter is not '1' || converted.Count != 0)) { converted.Add(letter); } @@ -1113,14 +1004,14 @@ public static bool IsValidPhoneNumber(this ReadOnlySpan input) /// /// /// - public static IEnumerable ExtractDialedNumbers(this string str) + public static ReadOnlySpan ExtractDialedNumbers(this string str) { return ExtractDialedNumbers(str.AsSpan()); } - public static IEnumerable ExtractDialedNumbers(ReadOnlySpan str) + public static ReadOnlySpan ExtractDialedNumbers(ReadOnlySpan str) { - List parsedNumbers = new(); + List parsedNumbers = []; if (!str.IsEmpty) { @@ -1129,7 +1020,7 @@ public static IEnumerable ExtractDialedNumbers(ReadOnlySpan str) foreach (char letter in str) { // Allow digits, drop leading 1's as NANPA area codes start at 201. - if (char.IsDigit(letter) && (letter is not '1' || converted.Any())) + if (char.IsDigit(letter) && (letter is not '1' || converted.Count != 0)) { converted.Add(letter); } @@ -1145,17 +1036,17 @@ public static IEnumerable ExtractDialedNumbers(ReadOnlySpan str) } } - return parsedNumbers; + return CollectionsMarshal.AsSpan(parsedNumbers); } - public static IEnumerable ExtractPhoneNumbers(this string str) + public static ReadOnlySpan ExtractPhoneNumbers(this string str) { return ExtractPhoneNumbers(str.AsSpan()); } - public static IEnumerable ExtractPhoneNumbers(this ReadOnlySpan str) + public static ReadOnlySpan ExtractPhoneNumbers(this ReadOnlySpan str) { - List parsedNumbers = new(); + List parsedNumbers = []; if (!str.IsEmpty) { @@ -1164,7 +1055,7 @@ public static IEnumerable ExtractPhoneNumbers(this ReadOnlySpan ExtractPhoneNumbers(this ReadOnlySpan /// A strongly typed representation of a NANPA phone number. /// - public class PhoneNumber + public readonly record struct PhoneNumber(ref readonly string DialedNumber, ref readonly int NPA, ref readonly int NXX, ref readonly int XXXX, ref readonly NumberType Type) { - public string DialedNumber { get; set; } = string.Empty; - public int NPA { get; set; } - public int NXX { get; set; } - public int XXXX { get; set; } - public NumberType Type { get; set; } - public DateTime DateIngested { get; set; } - public string IngestedFrom { get; set; } = string.Empty; - /// /// Parse a string into a strongly typed NANPA phone number. /// /// /// /// - public static bool TryParse(string input, out PhoneNumber number) + public static bool TryParse(in string input, out PhoneNumber number) { // Fail fast if (input.Length < 10 || string.IsNullOrWhiteSpace(input)) @@ -1234,7 +1118,12 @@ public static bool TryParse(string input, out PhoneNumber number) return true; } } - number = new(); + int emptyNPA = 0; + int emptyNXX = 0; + int emptyXXXX = 0; + NumberType invalid = NumberType.Invalid; + string dialed = string.Empty; + number = new(ref dialed, ref emptyNPA, ref emptyNXX, ref emptyXXXX, ref invalid); return false; } @@ -1246,7 +1135,12 @@ public static bool TryParse(string input, out PhoneNumber number) } else { - number = new(); + int emptyNPA = 0; + int emptyNXX = 0; + int emptyXXXX = 0; + NumberType invalid = NumberType.Invalid; + string dialed = string.Empty; + number = new(ref dialed, ref emptyNPA, ref emptyNXX, ref emptyXXXX, ref invalid); return false; } } @@ -1257,12 +1151,17 @@ public static bool TryParse(string input, out PhoneNumber number) /// /// /// True if a valid phone number was parsed and false if not. - public static bool TryParse(ReadOnlySpan input, out PhoneNumber number) + public static bool TryParse(in ReadOnlySpan input, out PhoneNumber number) { // Fail fast if (input.IsEmpty || input.Length < 10) { - number = new(); + int emptyNPA = 0; + int emptyNXX = 0; + int emptyXXXX = 0; + NumberType invalid = NumberType.Invalid; + string dialed = string.Empty; + number = new(ref dialed, ref emptyNPA, ref emptyNXX, ref emptyXXXX, ref invalid); return false; } @@ -1271,7 +1170,7 @@ public static bool TryParse(ReadOnlySpan input, out PhoneNumber number) foreach (char letter in input) { // Allow digits, drop leading 1's as NANPA area codes start at 201. - if (char.IsDigit(letter) && (letter is not '1' || converted.Any())) + if (char.IsDigit(letter) && (letter is not '1' || converted.Count != 0)) { converted.Add(letter); } @@ -1286,86 +1185,66 @@ public static bool TryParse(ReadOnlySpan input, out PhoneNumber number) // This input can't be parsed, so bail out. if (converted.Count is not 10) { - number = new(); + int emptyNPA = 0; + int emptyNXX = 0; + int emptyXXXX = 0; + NumberType invalid = NumberType.Invalid; + string dialed = string.Empty; + number = new(ref dialed, ref emptyNPA, ref emptyNXX, ref emptyXXXX, ref invalid); return false; } - Span cleanedQuery = CollectionsMarshal.AsSpan(converted); + ReadOnlySpan cleanedQuery = CollectionsMarshal.AsSpan(converted); bool checkNpa = int.TryParse(cleanedQuery[..3], out int npa); bool checkNxx = int.TryParse(cleanedQuery.Slice(3, 3), out int nxx); bool checkXxxx = int.TryParse(cleanedQuery.Slice(6, 4), out int xxxx); - bool checkValid = AreaCode.ValidPhoneNumber(npa, nxx, xxxx); + bool checkValid = AreaCode.ValidPhoneNumber(ref npa, ref nxx, ref xxxx); if (checkNpa && checkNxx && checkXxxx && checkValid) { - if (AreaCode.ValidTollfree(npa)) + if (AreaCode.ValidTollfree(ref npa)) { - number = new PhoneNumber - { - DialedNumber = cleanedQuery.ToString(), - NPA = npa, - NXX = nxx, - XXXX = xxxx, - Type = NumberType.Tollfree, - DateIngested = DateTime.Now - }; + string dialed = cleanedQuery.ToString(); + NumberType type = NumberType.Tollfree; + number = new PhoneNumber(ref dialed, ref npa, ref nxx, ref xxxx, ref type); } - else if (AreaCode.ValidNonGeographic(npa)) + else if (AreaCode.ValidNonGeographic(ref npa)) { - number = new PhoneNumber - { - DialedNumber = cleanedQuery.ToString(), - NPA = npa, - NXX = nxx, - XXXX = xxxx, - Type = NumberType.NonGeographic, - DateIngested = DateTime.Now - }; + string dialed = cleanedQuery.ToString(); + NumberType type = NumberType.NonGeographic; + number = new PhoneNumber(ref dialed, ref npa, ref nxx, ref xxxx, ref type); } - else if (AreaCode.ValidCanadian(npa)) + else if (AreaCode.ValidCanadian(ref npa)) { - number = new PhoneNumber - { - DialedNumber = cleanedQuery.ToString(), - NPA = npa, - NXX = nxx, - XXXX = xxxx, - Type = NumberType.Canada, - DateIngested = DateTime.Now - }; + string dialed = cleanedQuery.ToString(); + NumberType type = NumberType.Canada; + number = new PhoneNumber(ref dialed, ref npa, ref nxx, ref xxxx, ref type); } - else if (AreaCode.ValidCountryOrTerritory(npa)) + else if (AreaCode.ValidCountryOrTerritory(ref npa)) { - number = new PhoneNumber - { - DialedNumber = cleanedQuery.ToString(), - NPA = npa, - NXX = nxx, - XXXX = xxxx, - Type = NumberType.CountryOrTerritory, - DateIngested = DateTime.Now - }; + string dialed = cleanedQuery.ToString(); + NumberType type = NumberType.CountryOrTerritory; + number = new PhoneNumber(ref dialed, ref npa, ref nxx, ref xxxx, ref type); } else { - number = new PhoneNumber - { - DialedNumber = cleanedQuery.ToString(), - NPA = npa, - NXX = nxx, - XXXX = xxxx, - Type = NumberType.Local, - DateIngested = DateTime.Now - }; + string dialed = cleanedQuery.ToString(); + NumberType type = NumberType.CountryOrTerritory; + number = new PhoneNumber(ref dialed, ref npa, ref nxx, ref xxxx, ref type); } return true; } else { - number = new(); + int emptyNPA = 0; + int emptyNXX = 0; + int emptyXXXX = 0; + NumberType invalid = NumberType.Invalid; + string dialed = string.Empty; + number = new(ref dialed, ref emptyNPA, ref emptyNXX, ref emptyXXXX, ref invalid); return false; } } @@ -1382,11 +1261,11 @@ public static bool TryParseExact(ReadOnlySpan input, out PhoneNumber numbe bool checkNxx = int.TryParse(input.Slice(3, 3), out int nxx); bool checkXxxx = int.TryParse(input.Slice(6, 4), out int xxxx); - bool checkValid = AreaCode.ValidPhoneNumber(npa, nxx, xxxx); + bool checkValid = AreaCode.ValidPhoneNumber(ref npa, ref nxx, ref xxxx); if (checkNpa && checkNxx && checkXxxx && checkValid) { - if (AreaCode.ValidTollfree(npa)) + if (AreaCode.ValidTollfree(ref npa)) { number = new PhoneNumber { @@ -1395,10 +1274,9 @@ public static bool TryParseExact(ReadOnlySpan input, out PhoneNumber numbe NXX = nxx, XXXX = xxxx, Type = NumberType.Tollfree, - DateIngested = DateTime.Now }; } - else if (AreaCode.ValidNonGeographic(npa)) + else if (AreaCode.ValidNonGeographic(ref npa)) { number = new PhoneNumber { @@ -1407,10 +1285,9 @@ public static bool TryParseExact(ReadOnlySpan input, out PhoneNumber numbe NXX = nxx, XXXX = xxxx, Type = NumberType.NonGeographic, - DateIngested = DateTime.Now }; } - else if (AreaCode.ValidCanadian(npa)) + else if (AreaCode.ValidCanadian(ref npa)) { number = new PhoneNumber { @@ -1419,10 +1296,9 @@ public static bool TryParseExact(ReadOnlySpan input, out PhoneNumber numbe NXX = nxx, XXXX = xxxx, Type = NumberType.Canada, - DateIngested = DateTime.Now }; } - else if (AreaCode.ValidCountryOrTerritory(npa)) + else if (AreaCode.ValidCountryOrTerritory(ref npa)) { number = new PhoneNumber { @@ -1431,7 +1307,6 @@ public static bool TryParseExact(ReadOnlySpan input, out PhoneNumber numbe NXX = nxx, XXXX = xxxx, Type = NumberType.CountryOrTerritory, - DateIngested = DateTime.Now }; } else @@ -1443,7 +1318,6 @@ public static bool TryParseExact(ReadOnlySpan input, out PhoneNumber numbe NXX = nxx, XXXX = xxxx, Type = NumberType.Local, - DateIngested = DateTime.Now }; } @@ -1462,7 +1336,7 @@ public static bool TryParseExact(ReadOnlySpan input, out PhoneNumber numbe /// /// /// - public static bool TryParseShortCode(ReadOnlySpan input, out PhoneNumber number) + public static bool TryParseShortCode(in ReadOnlySpan input, out PhoneNumber number) { // Fail fast if (input.IsEmpty || input.Length < 5) @@ -1477,7 +1351,7 @@ public static bool TryParseShortCode(ReadOnlySpan input, out PhoneNumber n { // Allow digits. // Short codes cannot start with a 1 or a 0. - if (char.IsDigit(letter) && (letter is not '1' || converted.Any()) && (letter is not '0' || converted.Any())) + if (char.IsDigit(letter) && (letter is not '1' || converted.Count != 0) && (letter is not '0' || converted.Count != 0)) { converted.Add(letter); } @@ -1492,12 +1366,11 @@ public static bool TryParseShortCode(ReadOnlySpan input, out PhoneNumber n // This input can't be parsed, so bail out. if (converted.Count is 5 || converted.Count is 6) { - Span cleanedQuery = CollectionsMarshal.AsSpan(converted); + ReadOnlySpan cleanedQuery = CollectionsMarshal.AsSpan(converted); number = new() { DialedNumber = cleanedQuery.ToString(), Type = NumberType.ShortCode, - DateIngested = DateTime.Now, }; return true; } @@ -1556,9 +1429,11 @@ public static char LetterToKeypadDigit(char letter) /// public Uri GetAsURI() { - UriBuilder builder = new UriBuilder(); - builder.Scheme = "tel"; - builder.Path = $"+1-{GetNPAAsString()}-{GetNXXAsString()}-{GetXXXXAsString()}"; + UriBuilder builder = new() + { + Scheme = "tel", + Path = $"+1-{GetNPAAsString()}-{GetNXXAsString()}-{GetXXXXAsString()}" + }; return builder.Uri; } @@ -1566,7 +1441,10 @@ public Uri GetAsURI() /// Verifies that the phone number is valid. /// /// True if valid, false if invalid. - public bool IsValid() => DialedNumber.IsValidPhoneNumber() && AreaCode.ValidNPA(NPA) && AreaCode.ValidNXX(NXX) && AreaCode.ValidXXXX(XXXX); + public bool IsValid() + { + return DialedNumber.IsValidPhoneNumber() && AreaCode.ValidNPA(NPA) && AreaCode.ValidNXX(NXX) && AreaCode.ValidXXXX(XXXX); + } /// /// A string representation of the Phone Number object. diff --git a/PhoneNumbersNA/PhoneNumbersNA.csproj b/PhoneNumbersNA/PhoneNumbersNA.csproj index 1168dfa..95b5816 100644 --- a/PhoneNumbersNA/PhoneNumbersNA.csproj +++ b/PhoneNumbersNA/PhoneNumbersNA.csproj @@ -15,7 +15,7 @@ https://github.com/uncheckederror/PhoneNumbersNA https://github.com/uncheckederror/PhoneNumbersNA - 1.0.14 + 1.0.15 favicon-128.png README.md enable