From 0f4b0aa9b84f1cb938fc80da0cfb3baf388f04e1 Mon Sep 17 00:00:00 2001 From: David Retzlaff Date: Fri, 12 Apr 2024 11:30:43 +0200 Subject: [PATCH 1/8] Add option equality and spec --- Source/FunicularSwitch/Option.cs | 32 ++++++++++++++- .../Tests/FunicularSwitch.Test/OptionSpecs.cs | 40 +++++++++++++++++++ 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/Source/FunicularSwitch/Option.cs b/Source/FunicularSwitch/Option.cs index 829f849..3cbd829 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,34 @@ 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) + { + return _isSome == other._isSome && EqualityComparer.Default.Equals(_value, other._value); + } + + public override bool Equals(object? obj) + { + return obj is Option other && Equals(other); + } + + public override int GetHashCode() + { + unchecked + { + return (_isSome.GetHashCode() * 397) ^ EqualityComparer.Default.GetHashCode(_value); + } + } + + public static bool operator ==(Option left, Option right) + { + return left.Equals(right); + } + + public static bool operator !=(Option left, Option right) + { + return !left.Equals(right); + } } public static class OptionExtension @@ -191,5 +219,7 @@ public static Task> SelectMany(this Option result, Func result.Bind(t => selector(t).Map(t1 => resultSelector(t, t1))); #endregion + + } } \ No newline at end of file diff --git a/Source/Tests/FunicularSwitch.Test/OptionSpecs.cs b/Source/Tests/FunicularSwitch.Test/OptionSpecs.cs index 0a753d5..ffa5d1e 100644 --- a/Source/Tests/FunicularSwitch.Test/OptionSpecs.cs +++ b/Source/Tests/FunicularSwitch.Test/OptionSpecs.cs @@ -126,4 +126,44 @@ 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 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(); + } + + [TestMethod] + public void ShouldNotBeEqualIfOneIsNone() + { + var some1 = Some(1); + var some2 = None(); + some1.Equals(some2).Should().BeFalse(); + } } \ No newline at end of file From c0c3c0e35271cc7ba0f83ab08c4b7973ce353d65 Mon Sep 17 00:00:00 2001 From: David Retzlaff Date: Fri, 12 Apr 2024 11:31:52 +0200 Subject: [PATCH 2/8] Inc minor due to new methods --- Source/FunicularSwitch/FunicularSwitch.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From ef6b8a9c39a419ae47fc752e5aab412935445f01 Mon Sep 17 00:00:00 2001 From: David Retzlaff Date: Fri, 12 Apr 2024 11:35:59 +0200 Subject: [PATCH 3/8] Rm empty lines --- Source/FunicularSwitch/Option.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Source/FunicularSwitch/Option.cs b/Source/FunicularSwitch/Option.cs index 3cbd829..e9b6cb7 100644 --- a/Source/FunicularSwitch/Option.cs +++ b/Source/FunicularSwitch/Option.cs @@ -219,7 +219,5 @@ public static Task> SelectMany(this Option result, Func result.Bind(t => selector(t).Map(t1 => resultSelector(t, t1))); #endregion - - } } \ No newline at end of file From 727d1597883e4e678597c439e90964b4485cb4cf Mon Sep 17 00:00:00 2001 From: David Retzlaff Date: Fri, 12 Apr 2024 11:41:17 +0200 Subject: [PATCH 4/8] Rename var --- Source/Tests/FunicularSwitch.Test/OptionSpecs.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/Tests/FunicularSwitch.Test/OptionSpecs.cs b/Source/Tests/FunicularSwitch.Test/OptionSpecs.cs index ffa5d1e..f5be457 100644 --- a/Source/Tests/FunicularSwitch.Test/OptionSpecs.cs +++ b/Source/Tests/FunicularSwitch.Test/OptionSpecs.cs @@ -162,8 +162,8 @@ public void ShouldBeEqualIfBothValuesAreEqual() [TestMethod] public void ShouldNotBeEqualIfOneIsNone() { - var some1 = Some(1); - var some2 = None(); - some1.Equals(some2).Should().BeFalse(); + var some = Some(1); + var none = None(); + some.Equals(none).Should().BeFalse(); } } \ No newline at end of file From 2818e3831d85d8c13d9c715f68c229f1fbf653b8 Mon Sep 17 00:00:00 2001 From: Alexander Wiedemann Date: Fri, 12 Apr 2024 11:43:36 +0200 Subject: [PATCH 5/8] more option equality specs --- Source/FunicularSwitch.sln.DotSettings | 2 ++ Source/Tests/FunicularSwitch.Test/OptionSpecs.cs | 13 +++++++++++++ 2 files changed, 15 insertions(+) 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/Tests/FunicularSwitch.Test/OptionSpecs.cs b/Source/Tests/FunicularSwitch.Test/OptionSpecs.cs index ffa5d1e..e8272dd 100644 --- a/Source/Tests/FunicularSwitch.Test/OptionSpecs.cs +++ b/Source/Tests/FunicularSwitch.Test/OptionSpecs.cs @@ -166,4 +166,17 @@ public void ShouldNotBeEqualIfOneIsNone() var some2 = None(); some1.Equals(some2).Should().BeFalse(); } + + [TestMethod] + public void ShouldBeEqualIfBothAreNoneOfSameType() + { + None().Equals(None()).Should().BeTrue(); + } + + [TestMethod] + public void ShouldNotBeEqualIfBothAreNoneOfDifferentTypes() + { + // ReSharper disable once SuspiciousTypeConversion.Global + None().Equals(None()).Should().BeFalse(); + } } \ No newline at end of file From 2d285d8f7a045893661468e9beaabec97b6360cb Mon Sep 17 00:00:00 2001 From: Alexander Wiedemann Date: Fri, 12 Apr 2024 11:45:10 +0200 Subject: [PATCH 6/8] expression bodies --- Source/FunicularSwitch/Option.cs | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/Source/FunicularSwitch/Option.cs b/Source/FunicularSwitch/Option.cs index e9b6cb7..66610c2 100644 --- a/Source/FunicularSwitch/Option.cs +++ b/Source/FunicularSwitch/Option.cs @@ -112,15 +112,9 @@ public async Task Match(Func> some, TResult n public override string ToString() => Match(v => v?.ToString() ?? "", () => $"None {typeof(T).BeautifulName()}"); - public bool Equals(Option other) - { - return _isSome == other._isSome && EqualityComparer.Default.Equals(_value, other._value); - } + public bool Equals(Option other) => _isSome == other._isSome && EqualityComparer.Default.Equals(_value, other._value); - public override bool Equals(object? obj) - { - return obj is Option other && Equals(other); - } + public override bool Equals(object? obj) => obj is Option other && Equals(other); public override int GetHashCode() { @@ -130,15 +124,9 @@ public override int GetHashCode() } } - public static bool operator ==(Option left, Option right) - { - return left.Equals(right); - } + public static bool operator ==(Option left, Option right) => left.Equals(right); - public static bool operator !=(Option left, Option right) - { - return !left.Equals(right); - } + public static bool operator !=(Option left, Option right) => !left.Equals(right); } public static class OptionExtension From 43d266289855c2ae2ae9760c942dd274bf0fa31c Mon Sep 17 00:00:00 2001 From: Alexander Wiedemann Date: Fri, 12 Apr 2024 12:10:07 +0200 Subject: [PATCH 7/8] optimize option GetHashCode, more tests --- Source/FunicularSwitch/Option.cs | 8 +--- .../Tests/FunicularSwitch.Test/OptionSpecs.cs | 38 ++++++++++++++++++- 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/Source/FunicularSwitch/Option.cs b/Source/FunicularSwitch/Option.cs index 66610c2..e09c8ed 100644 --- a/Source/FunicularSwitch/Option.cs +++ b/Source/FunicularSwitch/Option.cs @@ -116,13 +116,7 @@ public async Task Match(Func> some, TResult n public override bool Equals(object? obj) => obj is Option other && Equals(other); - public override int GetHashCode() - { - unchecked - { - return (_isSome.GetHashCode() * 397) ^ EqualityComparer.Default.GetHashCode(_value); - } - } + public override int GetHashCode() => _isSome ? EqualityComparer.Default.GetHashCode(_value) : typeof(T).GetHashCode(); public static bool operator ==(Option left, Option right) => left.Equals(right); diff --git a/Source/Tests/FunicularSwitch.Test/OptionSpecs.cs b/Source/Tests/FunicularSwitch.Test/OptionSpecs.cs index b017117..3902702 100644 --- a/Source/Tests/FunicularSwitch.Test/OptionSpecs.cs +++ b/Source/Tests/FunicularSwitch.Test/OptionSpecs.cs @@ -134,6 +134,16 @@ public void ShouldHaveDifferentHashCodeIfBothValuesAreDifferent() 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() @@ -157,7 +167,9 @@ 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() @@ -173,10 +185,34 @@ 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); + nullOption.GetHashCode().Should().Be(0); + nullOption.Equals(Option.Some(new())).Should().BeFalse(); + nullOption.Equals(Option.Some(null)).Should().BeTrue(); + } + + class MyClass; } \ No newline at end of file From b21d4ed201f69e1ff78612e694af85b529a2894a Mon Sep 17 00:00:00 2001 From: Alexander Wiedemann Date: Fri, 12 Apr 2024 12:30:00 +0200 Subject: [PATCH 8/8] - different typed null valued options have different hashcodes --- Source/FunicularSwitch/Option.cs | 11 ++++++++++- Source/Tests/FunicularSwitch.Test/OptionSpecs.cs | 7 ++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/Source/FunicularSwitch/Option.cs b/Source/FunicularSwitch/Option.cs index e09c8ed..40d0991 100644 --- a/Source/FunicularSwitch/Option.cs +++ b/Source/FunicularSwitch/Option.cs @@ -116,7 +116,16 @@ public async Task Match(Func> some, TResult n public override bool Equals(object? obj) => obj is Option other && Equals(other); - public override int GetHashCode() => _isSome ? EqualityComparer.Default.GetHashCode(_value) : typeof(T).GetHashCode(); + 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); diff --git a/Source/Tests/FunicularSwitch.Test/OptionSpecs.cs b/Source/Tests/FunicularSwitch.Test/OptionSpecs.cs index 3902702..cb5b667 100644 --- a/Source/Tests/FunicularSwitch.Test/OptionSpecs.cs +++ b/Source/Tests/FunicularSwitch.Test/OptionSpecs.cs @@ -209,10 +209,15 @@ public void ShouldNotHaveSameHashCodeIfBothAreNoneOfDifferentTypes() public void NullOptionsWork() { var nullOption = Option.Some(null); - nullOption.GetHashCode().Should().Be(0); + 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