Skip to content

Commit

Permalink
Simplify equality logic
Browse files Browse the repository at this point in the history
Since a national significant number must be unique within a numbering plan, we only need to check the country and NSN.
  • Loading branch information
TrevorPilley committed Nov 4, 2024
1 parent a17dcec commit d0ce84b
Show file tree
Hide file tree
Showing 9 changed files with 291 additions and 220 deletions.
42 changes: 41 additions & 1 deletion src/PhoneNumbers/CountryInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace PhoneNumbers;
/// A class which contains country information related to phone numbers.
/// </summary>
[DebuggerDisplay("{" + nameof(GetDebuggerDisplay) + "(),nq}")]
public sealed partial class CountryInfo
public sealed partial class CountryInfo : IEquatable<CountryInfo>
{
internal const string Africa = "Africa";
internal const string Asia = "Asia";
Expand Down Expand Up @@ -122,6 +122,21 @@ internal CountryInfo()
s_emptyIntArray;
#endif

/// <inheritdoc/>
public static bool operator !=(CountryInfo? countryInfo1, CountryInfo? countryInfo2) =>
!(countryInfo1 == countryInfo2);

/// <inheritdoc/>
public static bool operator ==(CountryInfo? countryInfo1, CountryInfo? countryInfo2)
{
if (countryInfo1 is null)
{
return countryInfo2 is null;
}

return countryInfo1.Equals(countryInfo2);
}

internal static IEnumerable<CountryInfo> GetCountries() =>
typeof(CountryInfo)
.GetProperties(BindingFlags.Public | BindingFlags.Static)
Expand All @@ -141,6 +156,31 @@ internal static IEnumerable<CountryInfo> GetCountries(Func<CountryInfo, bool> pr
internal static PhoneNumberFormatter GetFormatter(string format) =>
s_formatters.SingleOrDefault(x => x.CanFormat(format)) ?? throw new FormatException($"{format} is not a supported format");

/// <inheritdoc/>
public override bool Equals(object? obj) =>
Equals(obj as CountryInfo);

/// <inheritdoc/>
public bool Equals(CountryInfo? other)
{
if (other is null)
{
return false;
}

if (ReferenceEquals(this, other))
{
return true;
}

return Iso3166Code.Equals(other.Iso3166Code, StringComparison.Ordinal);
}

/// <inheritdoc/>
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
public override int GetHashCode() =>
HashCode.Combine(Iso3166Code);

/// <summary>
/// Gets a value indicating whether the specified value has the calling code for this country.
/// </summary>
Expand Down
13 changes: 5 additions & 8 deletions src/PhoneNumbers/GeographicPhoneNumber.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,17 +66,14 @@ public bool Equals(GeographicPhoneNumber? other)
return true;
}

return Hint.Equals(other.Hint) &&
Country.Equals(other.Country) &&
GeographicArea.Equals(other.GeographicArea, StringComparison.Ordinal) &&
Kind.Equals(other.Kind) &&
(!HasNationalDestinationCode && !other.HasNationalDestinationCode || NationalDestinationCode!.Equals(other.NationalDestinationCode, StringComparison.Ordinal)) &&
NationalSignificantNumber.Equals(other.NationalSignificantNumber, StringComparison.Ordinal) &&
SubscriberNumber.Equals(other.SubscriberNumber, StringComparison.Ordinal);
// The National Significant Number (NSN) must be unique within a numbering plan so only
// where the countries match and the NSNs match they are the the same phone number.
return Country.Equals(other.Country) &&
NationalSignificantNumber.Equals(other.NationalSignificantNumber, StringComparison.Ordinal);
}

/// <inheritdoc/>
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
public override int GetHashCode() =>
HashCode.Combine(Hint, Country, GeographicArea, Kind, NationalSignificantNumber, NationalDestinationCode, SubscriberNumber);
HashCode.Combine(Country, NationalSignificantNumber);
}
12 changes: 5 additions & 7 deletions src/PhoneNumbers/MobilePhoneNumber.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,16 +64,14 @@ public bool Equals(MobilePhoneNumber? other)
return true;
}

return Hint.Equals(other.Hint) &&
Country.Equals(other.Country) &&
Kind.Equals(other.Kind) &&
(!HasNationalDestinationCode && !other.HasNationalDestinationCode || NationalDestinationCode!.Equals(other.NationalDestinationCode, StringComparison.Ordinal)) &&
NationalSignificantNumber.Equals(other.NationalSignificantNumber, StringComparison.Ordinal) &&
SubscriberNumber.Equals(other.SubscriberNumber, StringComparison.Ordinal);
// The National Significant Number (NSN) must be unique within a numbering plan so only
// where the countries match and the NSNs match they are the the same phone number.
return Country.Equals(other.Country) &&
NationalSignificantNumber.Equals(other.NationalSignificantNumber, StringComparison.Ordinal);
}

/// <inheritdoc/>
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
public override int GetHashCode() =>
HashCode.Combine(Hint, Country, Kind, NationalSignificantNumber, NationalDestinationCode, SubscriberNumber);
HashCode.Combine(Country, NationalSignificantNumber);
}
12 changes: 5 additions & 7 deletions src/PhoneNumbers/NonGeographicPhoneNumber.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,16 +78,14 @@ public bool Equals(NonGeographicPhoneNumber? other)
return true;
}

return Hint.Equals(other.Hint) &&
Country.Equals(other.Country) &&
Kind.Equals(other.Kind) &&
(!HasNationalDestinationCode && !other.HasNationalDestinationCode || NationalDestinationCode!.Equals(other.NationalDestinationCode, StringComparison.Ordinal)) &&
NationalSignificantNumber.Equals(other.NationalSignificantNumber, StringComparison.Ordinal) &&
SubscriberNumber.Equals(other.SubscriberNumber, StringComparison.Ordinal);
// The National Significant Number (NSN) must be unique within a numbering plan so only
// where the countries match and the NSNs match they are the the same phone number.
return Country.Equals(other.Country) &&
NationalSignificantNumber.Equals(other.NationalSignificantNumber, StringComparison.Ordinal);
}

/// <inheritdoc/>
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
public override int GetHashCode() =>
HashCode.Combine(Hint, Country, Kind, NationalSignificantNumber, NationalDestinationCode, SubscriberNumber);
HashCode.Combine(Country, NationalSignificantNumber);
}
95 changes: 95 additions & 0 deletions test/PhoneNumbers.Tests/CountryInfoTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,60 @@ public void GetFormatter_U_Returns_NationalUnformattedPhoneNumberFormatter() =>
public void GetFormatter_Throws_For_Invalid_Format() =>
Assert.Throws<FormatException>(() => CountryInfo.GetFormatter("X"));

[Fact]
public void Equality_Both_Null()
{
var countryInfo1 = default(CountryInfo);
var countryInfo2 = default(CountryInfo);

Assert.Equal(countryInfo1, countryInfo2);
Assert.True(countryInfo1 == countryInfo2);
Assert.True(countryInfo1 == (object)countryInfo2);
Assert.False(countryInfo1 != countryInfo2);
Assert.False(countryInfo1 != (object)countryInfo2);
}

[Fact]
public void Equality_Same_Instance()
{
var countryInfo1 = TestHelper.CreateCountryInfo();
var countryInfo2 = countryInfo1;

Assert.Equal(countryInfo1, countryInfo2);
Assert.True(countryInfo1.Equals(countryInfo2));
Assert.True(countryInfo1.Equals((object)countryInfo2));
Assert.True(countryInfo1 == countryInfo2);
Assert.True(countryInfo1 == (object)countryInfo2);
Assert.False(countryInfo1 != countryInfo2);
Assert.False(countryInfo1 != (object)countryInfo2);
}

[Fact]
public void Equality_Same_Iso3166Code()
{
var countryInfo1 = new CountryInfo
{
CallingCode = "-1",
Continent = "Pangea",
Iso3166Code = "YZ",
Name = "Nowhere",
};

var countryInfo2 = new CountryInfo
{
CallingCode = "-1",
Continent = "Pangea",
Iso3166Code = "YZ",
Name = "Nowhere",
};

Assert.Equal(countryInfo1, countryInfo2);
Assert.True(countryInfo1.Equals(countryInfo2));
Assert.True(countryInfo1.Equals((object)countryInfo2));
Assert.True(countryInfo1 == countryInfo2);
Assert.False(countryInfo1 != countryInfo2);
}

[Theory]
[InlineData(default(string))]
[InlineData("")]
Expand Down Expand Up @@ -64,6 +118,47 @@ public void HasTrunkPrefix_False() =>
public void HasTrunkPrefix_True() =>
Assert.True(TestHelper.CreateCountryInfo(trunkPrefix: "0").HasTrunkPrefix);

[Fact]
public void Inequality()
{
var countryInfo1 = new CountryInfo
{
CallingCode = "-1",
Continent = "Pangea",
Iso3166Code = "YZ",
Name = "Nowhere",
};

var countryInfo2 = default(CountryInfo);

Assert.NotEqual(countryInfo1, countryInfo2);
Assert.NotEqual(countryInfo2, countryInfo1);
Assert.False(countryInfo1.Equals(countryInfo2));
Assert.False(countryInfo1.Equals((object)countryInfo2));
Assert.False(countryInfo1 == countryInfo2);
Assert.False(countryInfo2 == countryInfo1);
Assert.False(countryInfo1 == (object)countryInfo2);
Assert.False(countryInfo2 == (object)countryInfo1);
Assert.True(countryInfo1 != countryInfo2);
Assert.True(countryInfo2 != countryInfo1);
Assert.True(countryInfo1 != (object)countryInfo2);
Assert.True(countryInfo2 != (object)countryInfo1);

// change the ISO3166 code
var countryInfo3 = new CountryInfo
{
CallingCode = "-1",
Continent = "Pangea",
Iso3166Code = "YY",
Name = "Nowhere",
};

Assert.NotEqual(countryInfo1, countryInfo3);
Assert.False(countryInfo1.Equals(countryInfo3));
Assert.False(countryInfo1 == countryInfo3);
Assert.True(countryInfo1 != countryInfo3);
}

[Theory]
[InlineData(default(string))]
[InlineData("")]
Expand Down
Loading

0 comments on commit d0ce84b

Please sign in to comment.