diff --git a/Source/FunicularSwitch.sln.DotSettings b/Source/FunicularSwitch.sln.DotSettings index e43f398..9c6402e 100644 --- a/Source/FunicularSwitch.sln.DotSettings +++ b/Source/FunicularSwitch.sln.DotSettings @@ -1,4 +1,6 @@  <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb_AaBb"><ExtraRule Prefix="" Suffix="" Style="AaBb_aaBb" /><ExtraRule Prefix="" Suffix="" Style="aaBb_aaBb" /></Policy> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Types and namespaces"><ElementKinds><Kind Name="NAMESPACE" /><Kind Name="CLASS" /><Kind Name="STRUCT" /><Kind Name="ENUM" /><Kind Name="DELEGATE" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb_AaBb"><ExtraRule Prefix="" Suffix="" Style="AaBb_aaBb" /><ExtraRule Prefix="" Suffix="" Style="aaBb_aaBb" /></Policy></Policy> + True True True \ No newline at end of file diff --git a/Source/FunicularSwitch/FunicularSwitch.csproj b/Source/FunicularSwitch/FunicularSwitch.csproj index cc08864..82b3317 100644 --- a/Source/FunicularSwitch/FunicularSwitch.csproj +++ b/Source/FunicularSwitch/FunicularSwitch.csproj @@ -14,7 +14,7 @@ 6 - 0.0 + 1.0 $(MajorVersion).0.0 diff --git a/Source/FunicularSwitch/Option.cs b/Source/FunicularSwitch/Option.cs index 829f849..40d0991 100644 --- a/Source/FunicularSwitch/Option.cs +++ b/Source/FunicularSwitch/Option.cs @@ -15,7 +15,7 @@ public static class Option public static Task> NoneAsync() => Task.FromResult(Option.None); } - public readonly struct Option : IEnumerable + public readonly struct Option : IEnumerable, IEquatable> { public static readonly Option None = default; @@ -111,6 +111,25 @@ public async Task Match(Func> some, TResult n public Option Convert() => Match(s => Option.Some((TOther)(object)s!), Option.None); public override string ToString() => Match(v => v?.ToString() ?? "", () => $"None {typeof(T).BeautifulName()}"); + + public bool Equals(Option other) => _isSome == other._isSome && EqualityComparer.Default.Equals(_value, other._value); + + public override bool Equals(object? obj) => obj is Option other && Equals(other); + + public override int GetHashCode() + { + unchecked + { + var hashCode = _isSome.GetHashCode(); + hashCode = (hashCode * 397) ^ EqualityComparer.Default.GetHashCode(_value); + hashCode = (hashCode * 397) ^ typeof(T).GetHashCode(); + return hashCode; + } + } + + public static bool operator ==(Option left, Option right) => left.Equals(right); + + public static bool operator !=(Option left, Option right) => !left.Equals(right); } public static class OptionExtension diff --git a/Source/Tests/FunicularSwitch.Test/OptionSpecs.cs b/Source/Tests/FunicularSwitch.Test/OptionSpecs.cs index 0a753d5..cb5b667 100644 --- a/Source/Tests/FunicularSwitch.Test/OptionSpecs.cs +++ b/Source/Tests/FunicularSwitch.Test/OptionSpecs.cs @@ -126,4 +126,98 @@ from r1 in some select x )).Should().BeEquivalentTo(await someAsync.Map(r => r * 2)); } + + [TestMethod] + public void ShouldHaveDifferentHashCodeIfBothValuesAreDifferent() + { + var hashcode1 = Some(1).GetHashCode(); + var hashcode2 = Some(2).GetHashCode(); + hashcode1.Should().NotBe(hashcode2); + } + + [TestMethod] + public void ShouldNotBeEqualIfValuesDifferent() + { + var some1 = Some(1); + var some2 = Some(2); + some1.Equals(some2).Should().BeFalse(); + (some1 == some2).Should().BeFalse(); + (some1 != some2).Should().BeTrue(); + } + + [TestMethod] + public void ShouldHaveSameHashCodeIfBothValueAreSame() + { + var hashcode1 = Some(1).GetHashCode(); + var hashcode2 = Some(1).GetHashCode(); + hashcode1.Should().Be(hashcode2); + } + + [TestMethod] + public void ShouldHaveDifferentHashCodeIfOneIsNone() + { + var hashcode1 = Some(1).GetHashCode(); + var hashcode2 = None().GetHashCode(); + hashcode1.Should().NotBe(hashcode2); + } + + [TestMethod] + public void ShouldBeEqualIfBothValuesAreEqual() + { + var some1 = Some(1); + var some2 = Some(1); + some1.Equals(some2).Should().BeTrue(); + (some1 == some2).Should().BeTrue(); + (some1 != some2).Should().BeFalse(); + } + + [TestMethod] + public void ShouldNotBeEqualIfOneIsNone() + { + var some = Some(1); + var none = None(); + some.Equals(none).Should().BeFalse(); + } + + [TestMethod] + public void ShouldBeEqualIfBothAreNoneOfSameType() + { + None().Equals(None()).Should().BeTrue(); + } + + [TestMethod] + public void ShouldHaveSameHashCodeIfBothAreNoneOfSameTypes() + { + None().GetHashCode().Equals(None().GetHashCode()).Should().BeTrue(); + } + + [TestMethod] + public void ShouldNotBeEqualIfBothAreNoneOfDifferentTypes() + { + // ReSharper disable once SuspiciousTypeConversion.Global + None().Equals(None()).Should().BeFalse(); + } + + [TestMethod] + public void ShouldNotHaveSameHashCodeIfBothAreNoneOfDifferentTypes() + { + // ReSharper disable once SuspiciousTypeConversion.Global + None().GetHashCode().Equals(None().GetHashCode()).Should().BeFalse(); + } + + [TestMethod] + public void NullOptionsWork() + { + var nullOption = Option.Some(null); + var nullOption2 = Option.Some(null); + nullOption.GetHashCode().Should().NotBe(nullOption2.GetHashCode()); + nullOption.Equals(Option.Some(new())).Should().BeFalse(); + // ReSharper disable once SuspiciousTypeConversion.Global + nullOption.Equals(nullOption2).Should().BeFalse(); + nullOption.Equals(Option.Some(null)).Should().BeTrue(); + } + + class MyClass; + + class MyOtherClass; } \ No newline at end of file