Skip to content

Commit 5a63a97

Browse files
authored
Implement proper equality comparers for classes with Set and Dictionary properties (confluentinc#2440)
* Add unit tests for equality and hash code of classes with Set/Dictionary members * Implement proper equality comparers for classes with Set and Dictionary properties
1 parent 6b281a8 commit 5a63a97

File tree

16 files changed

+942
-111
lines changed

16 files changed

+942
-111
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,4 @@ UpgradeLog*.htm
2424
core
2525

2626
*coverage.xml
27+
.DS_Store

Confluent.Kafka.sln

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProtobufEncryption", "examp
9595
EndProject
9696
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AvroGenericMigration", "examples\AvroGenericMigration\AvroGenericMigration.csproj", "{10CD6000-59A3-40C9-905F-20F4EE03C1B4}"
9797
EndProject
98+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{B544BD2C-F46D-4C68-8A31-3010E371E640}"
99+
ProjectSection(SolutionItems) = preProject
100+
src\Shared\DictionaryEqualityComparer.cs = src\Shared\DictionaryEqualityComparer.cs
101+
src\Shared\SetEqualityComparer.cs = src\Shared\SetEqualityComparer.cs
102+
EndProjectSection
103+
EndProject
98104
Global
99105
GlobalSection(SolutionConfigurationPlatforms) = preSolution
100106
Debug|Any CPU = Debug|Any CPU
@@ -666,5 +672,6 @@ Global
666672
{6727B941-3E07-4841-84E0-8EE47E04A3B3} = {9CE4B5F7-9251-4340-BACB-207066A5DBE8}
667673
{6988FB1F-3648-4E5E-821F-55D67CA00FD7} = {9CE4B5F7-9251-4340-BACB-207066A5DBE8}
668674
{10CD6000-59A3-40C9-905F-20F4EE03C1B4} = {9CE4B5F7-9251-4340-BACB-207066A5DBE8}
675+
{B544BD2C-F46D-4C68-8A31-3010E371E640} = {1EFCD839-0726-4BCE-B745-1E829991B1BC}
669676
EndGlobalSection
670677
EndGlobal

src/Confluent.SchemaRegistry.Encryption/Confluent.SchemaRegistry.Encryption.csproj

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,9 @@
3838
<None Include="..\..\README.md" Pack="true" PackagePath="\"/>
3939
</ItemGroup>
4040

41+
<ItemGroup>
42+
<Compile Include="..\Shared\SetEqualityComparer.cs" Link="Includes/SetEqualityComparer.cs"/>
43+
<Compile Include="..\Shared\DictionaryEqualityComparer.cs" Link="Includes/DictionaryEqualityComparer.cs"/>
44+
</ItemGroup>
45+
4146
</Project>

src/Confluent.SchemaRegistry.Encryption/Rest/DataContracts/Kek.cs

Lines changed: 56 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
using System;
1818
using System.Collections.Generic;
1919
using System.Runtime.Serialization;
20+
using Confluent.Shared.CollectionUtils;
2021

2122
namespace Confluent.SchemaRegistry.Encryption
2223
{
@@ -64,39 +65,72 @@ public class Kek : IEquatable<Kek>
6465
/// </summary>
6566
[DataMember(Name = "deleted")]
6667
public bool Deleted { get; set; }
67-
68+
69+
/// <inheritdoc />
6870
public bool Equals(Kek other)
6971
{
70-
if (ReferenceEquals(null, other)) return false;
71-
if (ReferenceEquals(this, other)) return true;
72-
return Name == other.Name && KmsType == other.KmsType &&
73-
KmsKeyId == other.KmsKeyId &&
74-
Utils.DictEquals(KmsProps, other.KmsProps) &&
75-
Doc == other.Doc && Shared == other.Shared && Deleted == other.Deleted;
72+
return KekEqualityComparer.Instance.Equals(this, other);
7673
}
77-
74+
75+
/// <inheritdoc />
7876
public override bool Equals(object obj)
7977
{
80-
if (ReferenceEquals(null, obj)) return false;
81-
if (ReferenceEquals(this, obj)) return true;
82-
if (obj.GetType() != this.GetType()) return false;
83-
return Equals((Kek)obj);
78+
return Equals(obj as Kek);
8479
}
8580

81+
/// <inheritdoc />
8682
public override int GetHashCode()
8783
{
88-
unchecked
84+
return KekEqualityComparer.Instance.GetHashCode(this);
85+
}
86+
87+
private class KekEqualityComparer : IEqualityComparer<Kek>
88+
{
89+
private readonly DictionaryEqualityComparer<string, string> kmsPropsEqualityComparer = new();
90+
91+
private KekEqualityComparer()
8992
{
90-
var hashCode = (Name != null ? Name.GetHashCode() : 0);
91-
hashCode = (hashCode * 397) ^ (KmsType != null ? KmsType.GetHashCode() : 0);
92-
hashCode = (hashCode * 397) ^ (KmsKeyId != null ? KmsKeyId.GetHashCode() : 0);
93-
hashCode = (hashCode * 397) ^ Utils.IEnumerableHashCode(KmsProps);
94-
hashCode = (hashCode * 397) ^ (Doc != null ? Doc.GetHashCode() : 0);
95-
hashCode = (hashCode * 397) ^ Shared.GetHashCode();
96-
hashCode = (hashCode * 397) ^ Deleted.GetHashCode();
97-
return hashCode;
9893
}
99-
}
94+
95+
public static KekEqualityComparer Instance { get; } = new();
96+
97+
public bool Equals(Kek x, Kek y)
98+
{
99+
if (ReferenceEquals(x, y)) return true;
100+
if (x is null) return false;
101+
if (y is null) return false;
102+
if (x.GetType() != y.GetType()) return false;
103+
104+
if (x.Name != y.Name) return false;
105+
if (x.KmsType != y.KmsType) return false;
106+
if (x.KmsKeyId != y.KmsKeyId) return false;
107+
if (!kmsPropsEqualityComparer.Equals(x.KmsProps, y.KmsProps)) return false;
108+
if (x.Doc != y.Doc) return false;
109+
if (x.Shared != y.Shared) return false;
110+
if (x.Deleted != y.Deleted) return false;
111+
112+
return true;
113+
}
100114

115+
public int GetHashCode(Kek obj)
116+
{
117+
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
118+
if (obj is null)
119+
{
120+
return 0;
121+
}
122+
123+
var hashCode = new HashCode();
124+
hashCode.Add(obj.Name);
125+
hashCode.Add(obj.KmsType);
126+
hashCode.Add(obj.KmsKeyId);
127+
hashCode.Add(obj.KmsProps, kmsPropsEqualityComparer);
128+
hashCode.Add(obj.Doc);
129+
hashCode.Add(obj.Shared);
130+
hashCode.Add(obj.Deleted);
131+
132+
return hashCode.ToHashCode();
133+
}
134+
}
101135
}
102136
}

src/Confluent.SchemaRegistry.Encryption/Rest/DataContracts/UpdateKek.cs

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
using System;
1818
using System.Collections.Generic;
1919
using System.Runtime.Serialization;
20+
using Confluent.Shared.CollectionUtils;
2021

2122
namespace Confluent.SchemaRegistry.Encryption
2223
{
@@ -41,29 +42,56 @@ public class UpdateKek : IEquatable<UpdateKek>
4142
[DataMember(Name = "shared")]
4243
public bool Shared { get; set; }
4344

45+
/// <inheritdoc />
4446
public bool Equals(UpdateKek other)
4547
{
46-
if (ReferenceEquals(null, other)) return false;
47-
if (ReferenceEquals(this, other)) return true;
48-
return Utils.DictEquals(KmsProps, other.KmsProps) && Doc == other.Doc && Shared == other.Shared;
48+
return UpdateKekEqualityComparer.Instance.Equals(this, other);
4949
}
5050

51+
/// <inheritdoc />
5152
public override bool Equals(object obj)
5253
{
53-
if (ReferenceEquals(null, obj)) return false;
54-
if (ReferenceEquals(this, obj)) return true;
55-
if (obj.GetType() != this.GetType()) return false;
56-
return Equals((UpdateKek)obj);
54+
return Equals(obj as UpdateKek);
5755
}
5856

57+
/// <inheritdoc />
5958
public override int GetHashCode()
6059
{
61-
unchecked
60+
return UpdateKekEqualityComparer.Instance.GetHashCode(this);
61+
}
62+
63+
private class UpdateKekEqualityComparer : IEqualityComparer<UpdateKek>
64+
{
65+
private readonly DictionaryEqualityComparer<string, string> kmsPropsEqualityComparer = new();
66+
67+
private UpdateKekEqualityComparer()
68+
{
69+
}
70+
71+
public static UpdateKekEqualityComparer Instance { get; } = new();
72+
73+
public bool Equals(UpdateKek x, UpdateKek y)
74+
{
75+
if (ReferenceEquals(x, y)) return true;
76+
if (x is null) return false;
77+
if (y is null) return false;
78+
if (x.GetType() != y.GetType()) return false;
79+
80+
if (!kmsPropsEqualityComparer.Equals(x.KmsProps, y.KmsProps)) return false;
81+
if (x.Doc != y.Doc) return false;
82+
if (x.Shared != y.Shared) return false;
83+
84+
return true;
85+
}
86+
87+
public int GetHashCode(UpdateKek obj)
6288
{
63-
var hashCode = Utils.IEnumerableHashCode(KmsProps);
64-
hashCode = (hashCode * 397) ^ (Doc != null ? Doc.GetHashCode() : 0);
65-
hashCode = (hashCode * 397) ^ Shared.GetHashCode();
66-
return hashCode;
89+
var hashCode = new HashCode();
90+
hashCode.Add(obj.KmsProps, kmsPropsEqualityComparer);
91+
hashCode.Add(obj.Doc);
92+
hashCode.Add(obj.Shared);
93+
94+
return hashCode.ToHashCode();
6795
}
6896
}
6997
}

src/Confluent.SchemaRegistry/Confluent.SchemaRegistry.csproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
</ItemGroup>
3030

3131
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
32+
<PackageReference Include="Microsoft.Bcl.HashCode" Version="6.0.0" />
3233
<PackageReference Include="System.Net.Http" Version="4.3.4" />
3334
</ItemGroup>
3435

@@ -42,4 +43,9 @@
4243
<None Include="..\..\README.md" Pack="true" PackagePath="\"/>
4344
</ItemGroup>
4445

46+
<ItemGroup>
47+
<Compile Include="..\Shared\SetEqualityComparer.cs" Link="Includes/SetEqualityComparer.cs"/>
48+
<Compile Include="..\Shared\DictionaryEqualityComparer.cs" Link="Includes/DictionaryEqualityComparer.cs"/>
49+
</ItemGroup>
50+
4551
</Project>

src/Confluent.SchemaRegistry/Rest/DataContracts/Metadata.cs

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
using System;
1818
using System.Collections.Generic;
1919
using System.Runtime.Serialization;
20+
using Confluent.Shared.CollectionUtils;
2021

2122
namespace Confluent.SchemaRegistry
2223
{
@@ -46,30 +47,56 @@ public Metadata(IDictionary<string, ISet<string>> tags,
4647
Sensitive = sensitive;
4748
}
4849

50+
/// <inheritdoc />
4951
public bool Equals(Metadata other)
5052
{
51-
if (ReferenceEquals(null, other)) return false;
52-
if (ReferenceEquals(this, other)) return true;
53-
return Utils.DictEquals(Tags, other.Tags) && Utils.DictEquals(Properties, other.Properties) &&
54-
Utils.SetEquals(Sensitive, other.Sensitive);
53+
return MetadataEqualityComparer.Instance.Equals(this, other);
5554
}
56-
55+
56+
/// <inheritdoc />
5757
public override bool Equals(object obj)
5858
{
59-
if (ReferenceEquals(null, obj)) return false;
60-
if (ReferenceEquals(this, obj)) return true;
61-
if (obj.GetType() != this.GetType()) return false;
62-
return Equals((Metadata)obj);
59+
return Equals(obj as Metadata);
6360
}
6461

62+
/// <inheritdoc />
6563
public override int GetHashCode()
6664
{
67-
unchecked
65+
return MetadataEqualityComparer.Instance.GetHashCode(this);
66+
}
67+
68+
private class MetadataEqualityComparer : IEqualityComparer<Metadata>
69+
{
70+
private readonly DictionaryEqualityComparer<string, ISet<string>> tagsEqualityComparer = new(new SetEqualityComparer<string>());
71+
private readonly DictionaryEqualityComparer<string, string> propertiesEqualityComparer = new();
72+
private readonly SetEqualityComparer<string> sensitiveEqualityComparer = new();
73+
74+
private MetadataEqualityComparer()
75+
{
76+
}
77+
78+
public static MetadataEqualityComparer Instance { get; } = new();
79+
80+
public bool Equals(Metadata x, Metadata y)
81+
{
82+
if (ReferenceEquals(x, y)) return true;
83+
if (x is null) return false;
84+
if (y is null) return false;
85+
if (x.GetType() != y.GetType()) return false;
86+
if (!tagsEqualityComparer.Equals(x.Tags, y.Tags)) return false;
87+
if (!propertiesEqualityComparer.Equals(x.Properties, y.Properties)) return false;
88+
if (!sensitiveEqualityComparer.Equals(x.Sensitive, y.Sensitive)) return false;
89+
90+
return true;
91+
}
92+
93+
public int GetHashCode(Metadata obj)
6894
{
69-
var hashCode = Utils.IEnumerableHashCode(Tags);
70-
hashCode = (hashCode * 397) ^ Utils.IEnumerableHashCode(Properties);
71-
hashCode = (hashCode * 397) ^ Utils.IEnumerableHashCode(Sensitive);
72-
return hashCode;
95+
var hashCode = new HashCode();
96+
hashCode.Add(obj.Tags, tagsEqualityComparer);
97+
hashCode.Add(obj.Properties, propertiesEqualityComparer);
98+
hashCode.Add(obj.Sensitive, sensitiveEqualityComparer);
99+
return hashCode.ToHashCode();
73100
}
74101
}
75102
}

0 commit comments

Comments
 (0)