Skip to content

Commit

Permalink
✨ Add back IEquatable with strict equality
Browse files Browse the repository at this point in the history
Fixes #1017

Reverted removing IEquatable and changed the implementation to strict equality and improved the xmldocs a bit.

Reverts commit f3c7e25.
"🔥 Remove IEquatable<T> and equality operators/methods"
  • Loading branch information
angularsen committed Nov 30, 2022
1 parent 23f8eaf commit b5eb9d5
Show file tree
Hide file tree
Showing 236 changed files with 11,572 additions and 354 deletions.
60 changes: 57 additions & 3 deletions CodeGen/Generators/UnitsNetGen/QuantityGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ namespace UnitsNet
Writer.W("IDecimalQuantity, ");
}

Writer.WL($"IComparable, IComparable<{_quantity.Name}>, IConvertible, IFormattable");
Writer.WL($"IEquatable<{_quantity.Name}>, IComparable, IComparable<{_quantity.Name}>, IConvertible, IFormattable");
Writer.WL($@"
{{
/// <summary>
Expand Down Expand Up @@ -752,7 +752,33 @@ private void GenerateEqualityAndComparison()
return left.Value > right.ToUnit(left.Unit).Value;
}}
/// <inheritdoc />
/// <summary>Returns true if both <see cref=""Value"" /> and <see cref=""Unit"" /> are exactly equal for both quantities.</summary>
/// <remarks>Consider using <see cref=""Equals({_quantity.Name}, double, ComparisonType)""/> for comparing floating point values with rounding error tolerances.</remarks>
public static bool operator ==({_quantity.Name} left, {_quantity.Name} right)
{{
return left.Equals(right);
}}
/// <summary>Returns true if either <see cref=""Value"" /> or <see cref=""Unit"" /> are not exactly equal for both quantities.</summary>
/// <remarks>Consider using <see cref=""Equals({_quantity.Name}, double, ComparisonType)""/> for comparing floating point values with rounding error tolerances.</remarks>
public static bool operator !=({_quantity.Name} left, {_quantity.Name} right)
{{
return !(left == right);
}}
/// <summary>Compares the current <see cref=""{_quantity.Name}""/> with another object of the same type and returns an integer that indicates whether the current instance precedes, follows, or occurs in the same position in the sort order as the other when converted to the same unit.</summary>
/// <param name=""obj"">An object to compare with this instance.</param>
/// <exception cref=""T:System.ArgumentException"">
/// <paramref name=""obj"" /> is not the same type as this instance.
/// </exception>
/// <returns>A value that indicates the relative order of the quantities being compared. The return value has these meanings:
/// <list type=""table"">
/// <listheader><term> Value</term><description> Meaning</description></listheader>
/// <item><term> Less than zero</term><description> This instance precedes <paramref name=""obj"" /> in the sort order.</description></item>
/// <item><term> Zero</term><description> This instance occurs in the same position in the sort order as <paramref name=""obj"" />.</description></item>
/// <item><term> Greater than zero</term><description> This instance follows <paramref name=""obj"" /> in the sort order.</description></item>
/// </list>
/// </returns>
public int CompareTo(object obj)
{{
if (obj is null) throw new ArgumentNullException(nameof(obj));
Expand All @@ -761,12 +787,40 @@ public int CompareTo(object obj)
return CompareTo(obj{_quantity.Name});
}}
/// <inheritdoc />
/// <summary>Compares the current <see cref=""{_quantity.Name}""/> with another <see cref=""{_quantity.Name}""/> and returns an integer that indicates whether the current instance precedes, follows, or occurs in the same position in the sort order as the other when converted to the same unit.</summary>
/// <param name=""other"">A quantity to compare with this instance.</param>
/// <returns>A value that indicates the relative order of the quantities being compared. The return value has these meanings:
/// <list type=""table"">
/// <listheader><term> Value</term><description> Meaning</description></listheader>
/// <item><term> Less than zero</term><description> This instance precedes <paramref name=""other"" /> in the sort order.</description></item>
/// <item><term> Zero</term><description> This instance occurs in the same position in the sort order as <paramref name=""other"" />.</description></item>
/// <item><term> Greater than zero</term><description> This instance follows <paramref name=""other"" /> in the sort order.</description></item>
/// </list>
/// </returns>
public int CompareTo({_quantity.Name} other)
{{
return _value.CompareTo(other.ToUnit(this.Unit).Value);
}}
/// <inheritdoc />
/// <summary>Returns true if either <see cref=""Value"" /> or <see cref=""Unit"" /> are not exactly equal for both quantities.</summary>
/// <remarks>Consider using <see cref=""Equals({_quantity.Name}, double, ComparisonType)""/> for comparing floating point values with rounding error tolerances.</remarks>
public override bool Equals(object obj)
{{
if(obj is null || !(obj is {_quantity.Name} obj{_quantity.Name}))
return false;
return Equals(obj{_quantity.Name});
}}
/// <inheritdoc />
/// <summary>Returns true if either <see cref=""Value"" /> or <see cref=""Unit"" /> are not exactly equal for both quantities.</summary>
/// <remarks>Consider using <see cref=""Equals({_quantity.Name}, double, ComparisonType)""/> for comparing floating point values with rounding error tolerances.</remarks>
public bool Equals({_quantity.Name} other)
{{
return new {{ Value, Unit }}.Equals(new {{ other.Value, other.Unit }});
}}
/// <summary>
/// <para>
/// Compare equality to another {_quantity.Name} within the given absolute or relative tolerance.
Expand Down
63 changes: 63 additions & 0 deletions CodeGen/Generators/UnitsNetGen/UnitTestBaseClassGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,33 @@ internal class UnitTestBaseClassGenerator : GeneratorBase
/// </summary>
private readonly string _numberSuffix;

/// <summary>
/// Other unit, if more than one unit exists for quantity, otherwise same as <see cref="_baseUnit"/>.
/// </summary>
private readonly Unit _otherOrBaseUnit;

/// <summary>
/// Example: "LengthUnit.Centimeter".
/// </summary>
private readonly string _otherOrBaseUnitFullName;

public UnitTestBaseClassGenerator(Quantity quantity)
{
_quantity = quantity;
_baseUnit = quantity.Units.FirstOrDefault(u => u.SingularName == _quantity.BaseUnit) ??
throw new ArgumentException($"No unit found with SingularName equal to BaseUnit [{_quantity.BaseUnit}]. This unit must be defined.",
nameof(quantity));

_unitEnumName = $"{quantity.Name}Unit";

_baseUnitEnglishAbbreviation = GetEnglishAbbreviation(_baseUnit);
_baseUnitFullName = $"{_unitEnumName}.{_baseUnit.SingularName}";
_numberSuffix = quantity.ValueType == "decimal" ? "m" : "";

// Try to pick another unit, or fall back to base unit if only a single unit.
_otherOrBaseUnit = quantity.Units.Where(u => u != _baseUnit).DefaultIfEmpty(_baseUnit).First();
_otherOrBaseUnitFullName = $"{_unitEnumName}.{_otherOrBaseUnit.SingularName}";

}

private string GetUnitFullName(Unit unit) => $"{_unitEnumName}.{unit.SingularName}";
Expand Down Expand Up @@ -483,6 +500,52 @@ public void CompareToThrowsOnNull()
Assert.Throws<ArgumentNullException>(() => {baseUnitVariableName}.CompareTo(null));
}}
[Theory]
[InlineData(1, {_baseUnitFullName}, 1, {_baseUnitFullName}, true)] // Same value and unit.
[InlineData(1, {_baseUnitFullName}, 2, {_baseUnitFullName}, false)] // Different value.
[InlineData(2, {_baseUnitFullName}, 1, {_otherOrBaseUnitFullName}, false)] // Different value and unit.");
if (_baseUnit != _otherOrBaseUnit)
{
Writer.WL($@"
[InlineData(1, {_baseUnitFullName}, 1, {_otherOrBaseUnitFullName}, false)] // Different unit.");
}
Writer.WL($@"
public void Equals_ReturnsTrue_IfValueAndUnitAreEqual({_quantity.ValueType} valueA, {_unitEnumName} unitA, {_quantity.ValueType} valueB, {_unitEnumName} unitB, bool expectEqual)
{{
var a = new {_quantity.Name}(valueA, unitA);
var b = new {_quantity.Name}(valueB, unitB);
// Operator overloads.
Assert.Equal(expectEqual, a == b);
Assert.Equal(expectEqual, b == a);
Assert.Equal(!expectEqual, a != b);
Assert.Equal(!expectEqual, b != a);
// IEquatable<T>
Assert.Equal(expectEqual, a.Equals(b));
Assert.Equal(expectEqual, b.Equals(a));
// IEquatable
Assert.Equal(expectEqual, a.Equals((object)b));
Assert.Equal(expectEqual, b.Equals((object)a));
}}
[Fact]
public void Equals_Null_ReturnsFalse()
{{
var a = {_quantity.Name}.Zero;
Assert.False(a.Equals((object)null));
// ""The result of the expression is always 'false'...""
#pragma warning disable CS8073
Assert.False(a == null);
Assert.False(null == a);
Assert.True(a != null);
Assert.True(null != a);
#pragma warning restore CS8073
}}
[Fact]
public void Equals_RelativeTolerance_IsImplemented()
{{
Expand Down
41 changes: 41 additions & 0 deletions UnitsNet.Tests/GeneratedCode/TestsBase/AccelerationTestsBase.g.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

41 changes: 41 additions & 0 deletions UnitsNet.Tests/GeneratedCode/TestsBase/AngleTestsBase.g.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit b5eb9d5

Please sign in to comment.