Skip to content

Commit 8d14aad

Browse files
Implement CollectionHelper.GetHashCode that accepts IEqualityComparer<T>
- Also fix CollectionHelper.GetHashCode to account for the length of the collections Co-authored-by: Frédéric Delaporte <[email protected]>
1 parent af31165 commit 8d14aad

File tree

9 files changed

+152
-49
lines changed

9 files changed

+152
-49
lines changed

src/NHibernate.Test/NHSpecificTest/NH3956/Fixture.cs

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -205,18 +205,5 @@ public void NativeSQLQuerySpecificationInequalityOnSpaceLengthes()
205205
Assert.IsFalse(spec1.Equals(spec2));
206206
Assert.IsFalse(spec2.Equals(spec1));
207207
}
208-
209-
[Test]
210-
public void NativeSQLQuerySpecificationInequalityOnSpaceOrderings()
211-
{
212-
var spec1 = new NativeSQLQuerySpecification("select blah", null,
213-
new[] { "space1", "space2" });
214-
var spec2 = new NativeSQLQuerySpecification("select blah", null,
215-
new[] { "space2", "space1" });
216-
217-
TweakHashcode(spec2, spec1.GetHashCode());
218-
Assert.IsFalse(spec1.Equals(spec2));
219-
Assert.IsFalse(spec2.Equals(spec1));
220-
}
221208
}
222209
}

src/NHibernate.Test/UtilityTest/CollectionHelperFixture.cs

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
using System.Collections.Generic;
1+
using System;
2+
using System.Collections.Generic;
3+
using NHibernate.Engine;
24
using NHibernate.Util;
35
using NUnit.Framework;
46

@@ -180,6 +182,69 @@ public void SetsWithoutSameCountShouldBeInequal()
180182
Assert.That(CollectionHelper.SetEquals(c2, c1), Is.False);
181183
}
182184

185+
[Test]
186+
public void MapsWithSameContentShouldHaveSameHashCode()
187+
{
188+
var d1 = new Dictionary<string, string> { { "1", "2" }, { "3", "4" } };
189+
var d2 = new Dictionary<string, string> { { "1", "2" }, { "3", "4" } };
190+
Assert.That(
191+
CollectionHelper.GetHashCode(d1, KvpStringComparer.Instance),
192+
Is.EqualTo(CollectionHelper.GetHashCode(d2, KvpStringComparer.Instance)));
193+
}
194+
195+
// Failure of following tests is not an error from GetHashCode semantic viewpoint, but it causes it
196+
// to be potentially inefficients for usages in dictionnaries or hashset.
197+
198+
[Test]
199+
public void MapsWithSameCountButDistinctKeysShouldNotHaveSameHashCode()
200+
{
201+
var d1 = new Dictionary<string, string> { { "1", "2" }, { "3", "4" } };
202+
var d2 = new Dictionary<string, string> { { "1", "2" }, { "4", "3" } };
203+
Assert.That(
204+
CollectionHelper.GetHashCode(d1, KvpStringComparer.Instance),
205+
Is.Not.EqualTo(CollectionHelper.GetHashCode(d2, KvpStringComparer.Instance)));
206+
}
207+
208+
[Test]
209+
public void MapsWithSameCountButDistinctValuesShouldNotHaveSameHashCode()
210+
{
211+
var d1 = new Dictionary<string, string> { { "1", "2" }, { "3", "4" } };
212+
var d2 = new Dictionary<string, string> { { "1", "2" }, { "3", "3" } };
213+
Assert.That(
214+
CollectionHelper.GetHashCode(d1, KvpStringComparer.Instance),
215+
Is.Not.EqualTo(CollectionHelper.GetHashCode(d2, KvpStringComparer.Instance)));
216+
}
217+
218+
[Test]
219+
public void MapsWithoutSameCountShouldNotHaveSameHashCode()
220+
{
221+
var d1 = new Dictionary<string, string> { { "1", "2" }, { "3", "4" } };
222+
var d2 = new Dictionary<string, string> { { "1", "2" } };
223+
Assert.That(
224+
CollectionHelper.GetHashCode(d1, KvpStringComparer.Instance),
225+
Is.Not.EqualTo(CollectionHelper.GetHashCode(d2, KvpStringComparer.Instance)));
226+
}
227+
228+
internal class KvpStringComparer : IEqualityComparer<KeyValuePair<string, string>>
229+
{
230+
public static readonly KvpStringComparer Instance = new KvpStringComparer();
231+
232+
public bool Equals(KeyValuePair<string, string> x, KeyValuePair<string, string> y)
233+
{
234+
return StringComparer.Ordinal.Equals(x.Key, y.Key) &&
235+
StringComparer.Ordinal.Equals(x.Value, y.Value);
236+
}
237+
238+
public int GetHashCode(KeyValuePair<string, string> obj)
239+
{
240+
unchecked
241+
{
242+
return 397 * StringComparer.Ordinal.GetHashCode(obj.Key) ^
243+
(obj.Value != null ? StringComparer.Ordinal.GetHashCode(obj.Value) : 0);
244+
}
245+
}
246+
}
247+
183248
#endregion
184249

185250
#region List/Sequence

src/NHibernate/Cache/FilterKey.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ public FilterKey(string name, IEnumerable<KeyValuePair<string, object>> @params,
2525

2626
public override int GetHashCode()
2727
{
28-
int result = 13;
28+
var result = 13;
2929
result = 37 * result + _filterName.GetHashCode();
30-
result = 37 * result + CollectionHelper.GetHashCode(_filterParameters);
30+
result = 37 * result + CollectionHelper.GetHashCode(_filterParameters, NamedParameterComparer.Instance);
3131
return result;
3232
}
3333

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using NHibernate.Engine;
4+
5+
namespace NHibernate.Cache
6+
{
7+
internal class NamedParameterComparer : IEqualityComparer<KeyValuePair<string, TypedValue>>
8+
{
9+
public static readonly NamedParameterComparer Instance = new NamedParameterComparer();
10+
11+
public bool Equals(KeyValuePair<string, TypedValue> x, KeyValuePair<string, TypedValue> y)
12+
{
13+
return StringComparer.Ordinal.Equals(x.Key, y.Key) &&
14+
(ReferenceEquals(x.Value, y.Value) || x.Value != null && y.Value != null && x.Value.Equals(y.Value));
15+
}
16+
17+
public int GetHashCode(KeyValuePair<string, TypedValue> obj)
18+
{
19+
unchecked
20+
{
21+
return 397 * StringComparer.Ordinal.GetHashCode(obj.Key) ^
22+
(obj.Value != null ? obj.Value.GetHashCode() : 0);
23+
}
24+
}
25+
}
26+
}

src/NHibernate/Cache/QueryKey.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ public int ComputeHashCode()
161161
result = 37 * result + _firstRow.GetHashCode();
162162
result = 37 * result + _maxRows.GetHashCode();
163163

164-
result = 37 * result + (_namedParameters == null ? 0 : CollectionHelper.GetHashCode(_namedParameters));
164+
result = 37 * result + (_namedParameters == null ? 0 : CollectionHelper.GetHashCode(_namedParameters, NamedParameterComparer.Instance));
165165

166166
for (int i = 0; i < _types.Length; i++)
167167
{

src/NHibernate/Engine/Query/QueryPlanCache.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -214,9 +214,9 @@ protected HQLQueryPlanKey(System.Type queryTypeDiscriminator, string query, bool
214214
unchecked
215215
{
216216
var hash = query.GetHashCode();
217-
hash = 29*hash + (shallow ? 1 : 0);
218-
hash = 29*hash + CollectionHelper.GetHashCode(filterNames);
219-
hash = 29*hash + queryTypeDiscriminator.GetHashCode();
217+
hash = 29 * hash + (shallow ? 1 : 0);
218+
hash = 29 * hash + CollectionHelper.GetHashCode(filterNames, filterNames.Comparer);
219+
hash = 29 * hash + queryTypeDiscriminator.GetHashCode();
220220
hashCode = hash;
221221
}
222222
}
@@ -289,7 +289,7 @@ public FilterQueryPlanKey(string query, string collectionRole, bool shallow, IDi
289289
int hash = query.GetHashCode();
290290
hash = 29 * hash + collectionRole.GetHashCode();
291291
hash = 29 * hash + (shallow ? 1 : 0);
292-
hash = 29 * hash + CollectionHelper.GetHashCode(filterNames);
292+
hash = 29 * hash + CollectionHelper.GetHashCode(filterNames, filterNames.Comparer);
293293
hashCode = hash;
294294
}
295295

src/NHibernate/Engine/Query/Sql/NativeSQLQuerySpecification.cs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System;
12
using System.Collections.Generic;
23
using NHibernate.Util;
34

@@ -7,7 +8,7 @@ public class NativeSQLQuerySpecification
78
{
89
private readonly string queryString;
910
private readonly INativeSQLQueryReturn[] sqlQueryReturns;
10-
private readonly ISet<string> querySpaces;
11+
private readonly HashSet<string> querySpaces;
1112
private readonly int hashCode;
1213

1314
public NativeSQLQuerySpecification(
@@ -26,10 +27,10 @@ public NativeSQLQuerySpecification(
2627
int hCode = queryString.GetHashCode();
2728
unchecked
2829
{
29-
hCode = 29 * hCode + CollectionHelper.GetHashCode(this.querySpaces);
30+
hCode = 29 * hCode + CollectionHelper.GetHashCode(this.querySpaces, this.querySpaces.Comparer);
3031
if (this.sqlQueryReturns != null)
3132
{
32-
hCode = 29 * hCode + CollectionHelper.GetHashCode(this.sqlQueryReturns);
33+
hCode = 29 * hCode + ArrayHelper.ArrayGetHashCode(this.sqlQueryReturns);
3334
}
3435
}
3536

@@ -63,10 +64,11 @@ public override bool Equals(object obj)
6364

6465
// NH-3956: hashcode inequality rules out equality, but hashcode equality is not enough.
6566
// Code taken back from 8e92af3f and amended according to NH-1931.
66-
return hashCode == that.hashCode &&
67+
return
68+
hashCode == that.hashCode &&
6769
queryString.Equals(that.queryString) &&
68-
CollectionHelper.SequenceEquals(querySpaces, that.querySpaces) &&
69-
CollectionHelper.SequenceEquals<INativeSQLQueryReturn>(sqlQueryReturns, that.sqlQueryReturns);
70+
CollectionHelper.SetEquals(querySpaces, that.querySpaces) &&
71+
ArrayHelper.ArrayEquals(sqlQueryReturns, that.sqlQueryReturns);
7072
}
7173

7274
public override int GetHashCode()

src/NHibernate/Mapping/Table.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1083,14 +1083,13 @@ public bool Equals(ForeignKeyKey x, ForeignKeyKey y)
10831083
{
10841084
// NH : Different implementation to prevent NH930 (look test)
10851085
return //y.referencedClassName.Equals(x.referencedClassName) &&
1086-
CollectionHelper.SequenceEquals<Column>(y.columns, x.columns)
1087-
&& CollectionHelper.SequenceEquals<Column>(y.referencedColumns, x.referencedColumns);
1086+
CollectionHelper.SequenceEquals(y.columns, x.columns)
1087+
&& CollectionHelper.SequenceEquals(y.referencedColumns, x.referencedColumns);
10881088
}
10891089

10901090
public int GetHashCode(ForeignKeyKey obj)
10911091
{
1092-
int result = CollectionHelper.GetHashCode(obj.columns) ^ CollectionHelper.GetHashCode(obj.referencedColumns);
1093-
return result;
1092+
return CollectionHelper.GetHashCode(obj.columns) ^ CollectionHelper.GetHashCode(obj.referencedColumns);
10941093
}
10951094

10961095
#endregion

src/NHibernate/Util/CollectionHelper.cs

Lines changed: 42 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -305,27 +305,26 @@ public static bool DictionaryEquals(IDictionary a, IDictionary b)
305305
/// <summary>
306306
/// Computes a hash code for <paramref name="coll"/>.
307307
/// </summary>
308-
/// <remarks>The hash code is computed as the sum of hash codes of
309-
/// individual elements, so that the value is independent of the
308+
/// <remarks>The hash code is computed as the sum of hash codes of individual elements
309+
/// plus a length of the collection, so that the value is independent of the
310310
/// collection iteration order.
311311
/// </remarks>
312312
[Obsolete("It has no more usages in NHibernate and will be removed in a future version.")]
313313
public static int GetHashCode(IEnumerable coll)
314314
{
315-
unchecked
316-
{
317-
int result = 0;
315+
var result = 0;
318316

319-
foreach (object obj in coll)
317+
foreach (var obj in coll)
318+
{
319+
unchecked
320320
{
321321
if (obj != null)
322-
{
323322
result += obj.GetHashCode();
324-
}
323+
result++;
325324
}
326-
327-
return result;
328325
}
326+
327+
return result;
329328
}
330329

331330
/// <summary>
@@ -582,24 +581,49 @@ public IEnumerator GetEnumerator()
582581
/// <summary>
583582
/// Computes a hash code for <paramref name="coll"/>.
584583
/// </summary>
585-
/// <remarks>The hash code is computed as the sum of hash codes of
586-
/// individual elements, so that the value is independent of the
584+
/// <remarks>The hash code is computed as the sum of hash codes of individual elements
585+
/// plus a length of the collection, so that the value is independent of the
587586
/// collection iteration order.
588587
/// </remarks>
589588
public static int GetHashCode<T>(IEnumerable<T> coll)
590589
{
591-
unchecked
592-
{
593-
int result = 0;
590+
var result = 0;
594591

595-
foreach (T obj in coll)
592+
foreach (var obj in coll)
593+
{
594+
unchecked
596595
{
597-
if (!obj.Equals(default(T)))
596+
if (!ReferenceEquals(obj, null))
598597
result += obj.GetHashCode();
598+
result++;
599599
}
600+
}
601+
602+
return result;
603+
}
600604

601-
return result;
605+
/// <summary>
606+
/// Computes a hash code for <paramref name="coll"/>.
607+
/// </summary>
608+
/// <remarks>The hash code is computed as the sum of hash codes of individual elements
609+
/// plus a length of the collection, so that the value is independent of the
610+
/// collection iteration order.
611+
/// </remarks>
612+
public static int GetHashCode<T>(IEnumerable<T> coll, IEqualityComparer<T> comparer)
613+
{
614+
var result = 0;
615+
616+
foreach (var obj in coll)
617+
{
618+
unchecked
619+
{
620+
if (!ReferenceEquals(obj, null))
621+
result += comparer.GetHashCode(obj);
622+
result++;
623+
}
602624
}
625+
626+
return result;
603627
}
604628

605629
/// <summary>

0 commit comments

Comments
 (0)