diff --git a/src/NHibernate.Test/NHSpecificTest/NH3957/ResultTransformerEqualityFixture.cs b/src/NHibernate.Test/NHSpecificTest/NH3957/ResultTransformerEqualityFixture.cs index cd0db9a6f14..d1900b57abc 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH3957/ResultTransformerEqualityFixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH3957/ResultTransformerEqualityFixture.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Reflection; using NHibernate.Transform; using NUnit.Framework; @@ -8,53 +7,20 @@ namespace NHibernate.Test.NHSpecificTest.NH3957 [TestFixture] public class ResultTransformerEqualityFixture { - /// - /// Allows to simulate a hashcode collision. Issue would be unpractical to test otherwise. - /// Hashcode collision must be supported for avoiding unexpected and hard to reproduce failures. - /// - private void TweakHashcode(System.Type transformerToTweak, object hasher) - { - var hasherTargetField = transformerToTweak.GetField("Hasher", BindingFlags.Static | BindingFlags.NonPublic); - if (!_hasherBackup.ContainsKey(transformerToTweak)) - _hasherBackup.Add(transformerToTweak, hasherTargetField.GetValue(null)); - - // Though hasher is a readonly field, this works at the time of this writing. If it starts breaking and cannot be fixed, - // ignore those tests or throw them away. - hasherTargetField.SetValue(null, hasher); - } - - private Dictionary _hasherBackup = new Dictionary(); - - [SetUp] - public void Setup() - { - var hasherForAll = typeof(AliasToEntityMapResultTransformer) - .GetField("Hasher", BindingFlags.Static | BindingFlags.NonPublic) - .GetValue(null); - TweakHashcode(typeof(DistinctRootEntityResultTransformer), hasherForAll); - TweakHashcode(typeof(PassThroughResultTransformer), hasherForAll); - TweakHashcode(typeof(RootEntityResultTransformer), hasherForAll); - TweakHashcode(typeof(ToListResultTransformer), hasherForAll); - } - - [TearDown] - public void TearDown() - { - // Restore those types hashcode. (Avoid impacting perf of other tests, avoid second level query cache - // issues if it was holding cached entries (but would mean some tests have not cleaned up properly).) - foreach(var backup in _hasherBackup) - { - TweakHashcode(backup.Key, backup.Value); - } - } - + public class CustomAliasToEntityMapResultTransformer : AliasToEntityMapResultTransformer { } + public class CustomDistinctRootEntityResultTransformer : DistinctRootEntityResultTransformer { } + public class CustomPassThroughResultTransformer : PassThroughResultTransformer { } + public class CustomRootEntityResultTransformer : RootEntityResultTransformer { } + // Non reg test case [Test] public void AliasToEntityMapEquality() { var transf1 = new AliasToEntityMapResultTransformer(); var transf2 = new AliasToEntityMapResultTransformer(); + HashSet set = new HashSet() { transf1, transf2, }; + Assert.That(set.Count, Is.EqualTo(1)); Assert.IsTrue(transf1.Equals(transf2)); Assert.IsTrue(transf2.Equals(transf1)); } @@ -63,8 +29,11 @@ public void AliasToEntityMapEquality() public void AliasToEntityMapAndDistinctRootEntityInequality() { var transf1 = new AliasToEntityMapResultTransformer(); - var transf2 = new DistinctRootEntityResultTransformer(); + var transf2 = new CustomAliasToEntityMapResultTransformer(); + HashSet set = new HashSet() { transf1, transf2, }; + Assert.That(transf1.GetHashCode(), Is.EqualTo(transf2.GetHashCode()), "prerequisite"); + Assert.That(set.Count, Is.EqualTo(2)); Assert.IsFalse(transf1.Equals(transf2)); Assert.IsFalse(transf2.Equals(transf1)); } @@ -75,18 +44,35 @@ public void DistinctRootEntityEquality() { var transf1 = new DistinctRootEntityResultTransformer(); var transf2 = new DistinctRootEntityResultTransformer(); + HashSet set = new HashSet() { transf1, transf2, }; + Assert.That(set.Count, Is.EqualTo(1)); Assert.IsTrue(transf1.Equals(transf2)); Assert.IsTrue(transf2.Equals(transf1)); } + [Test] + public void DistinctRootEntityEqualityInequality() + { + var transf1 = new DistinctRootEntityResultTransformer(); + var transf2 = new CustomDistinctRootEntityResultTransformer(); + HashSet set = new HashSet() { transf1, transf2, }; + + Assert.That(transf1.GetHashCode(), Is.EqualTo(transf2.GetHashCode()), "prerequisite"); + Assert.That(set.Count, Is.EqualTo(2)); + Assert.IsFalse(transf1.Equals(transf2)); + Assert.IsFalse(transf2.Equals(transf1)); + } + // Non reg test case [Test] public void PassThroughEquality() { var transf1 = new PassThroughResultTransformer(); var transf2 = new PassThroughResultTransformer(); + HashSet set = new HashSet() { transf1, transf2, }; + Assert.That(set.Count, Is.EqualTo(1)); Assert.IsTrue(transf1.Equals(transf2)); Assert.IsTrue(transf2.Equals(transf1)); } @@ -95,8 +81,11 @@ public void PassThroughEquality() public void PassThroughAndRootEntityInequality() { var transf1 = new PassThroughResultTransformer(); - var transf2 = new RootEntityResultTransformer(); - + var transf2 = new CustomPassThroughResultTransformer(); + HashSet set = new HashSet() { transf1, transf2, }; + + Assert.That(transf1.GetHashCode(), Is.EqualTo(transf2.GetHashCode()), "prerequisite"); + Assert.That(set.Count, Is.EqualTo(2)); Assert.IsFalse(transf1.Equals(transf2)); Assert.IsFalse(transf2.Equals(transf1)); } @@ -107,7 +96,9 @@ public void RootEntityEquality() { var transf1 = new RootEntityResultTransformer(); var transf2 = new RootEntityResultTransformer(); + HashSet set = new HashSet() { transf1, transf2, }; + Assert.That(set.Count, Is.EqualTo(1)); Assert.IsTrue(transf1.Equals(transf2)); Assert.IsTrue(transf2.Equals(transf1)); } @@ -116,8 +107,11 @@ public void RootEntityEquality() public void RootEntityAndToListInequality() { var transf1 = new RootEntityResultTransformer(); - var transf2 = new ToListResultTransformer(); - + var transf2 = new CustomRootEntityResultTransformer(); + HashSet set = new HashSet() { transf1, transf2, }; + + Assert.That(transf1.GetHashCode(), Is.EqualTo(transf2.GetHashCode()), "prerequisite"); + Assert.That(set.Count, Is.EqualTo(2)); Assert.IsFalse(transf1.Equals(transf2)); Assert.IsFalse(transf2.Equals(transf1)); } @@ -128,9 +122,11 @@ public void ToListEquality() { var transf1 = new ToListResultTransformer(); var transf2 = new ToListResultTransformer(); + HashSet set = new HashSet() { transf1, transf2 }; + Assert.That(set.Count, Is.EqualTo(1)); Assert.IsTrue(transf1.Equals(transf2)); Assert.IsTrue(transf2.Equals(transf1)); } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Criterion/CriteriaSpecification.cs b/src/NHibernate/Criterion/CriteriaSpecification.cs index a274a6cbeb3..a4d2505a891 100644 --- a/src/NHibernate/Criterion/CriteriaSpecification.cs +++ b/src/NHibernate/Criterion/CriteriaSpecification.cs @@ -31,10 +31,10 @@ public static class CriteriaSpecification static CriteriaSpecification() { - AliasToEntityMap = new AliasToEntityMapResultTransformer(); - RootEntity = new RootEntityResultTransformer(); - DistinctRootEntity = new DistinctRootEntityResultTransformer(); - Projection = new PassThroughResultTransformer(); + AliasToEntityMap = AliasToEntityMapResultTransformer.Instance; + RootEntity = RootEntityResultTransformer.Instance; + DistinctRootEntity = DistinctRootEntityResultTransformer.Instance; + Projection = PassThroughResultTransformer.Instance; InnerJoin = JoinType.InnerJoin; FullJoin = JoinType.FullJoin; LeftJoin = JoinType.LeftOuterJoin; diff --git a/src/NHibernate/Transform/AliasToEntityMapResultTransformer.cs b/src/NHibernate/Transform/AliasToEntityMapResultTransformer.cs index 3fe474856fe..0497d292a51 100644 --- a/src/NHibernate/Transform/AliasToEntityMapResultTransformer.cs +++ b/src/NHibernate/Transform/AliasToEntityMapResultTransformer.cs @@ -6,7 +6,7 @@ namespace NHibernate.Transform [Serializable] public class AliasToEntityMapResultTransformer : AliasedTupleSubsetResultTransformer { - private static readonly object Hasher = new object(); + internal static readonly AliasToEntityMapResultTransformer Instance = new AliasToEntityMapResultTransformer(); public override object TransformTuple(object[] tuple, string[] aliases) { @@ -37,18 +37,16 @@ public override bool IsTransformedValueATupleElement(string[] aliases, int tuple public override bool Equals(object obj) { - if (obj == null || obj.GetHashCode() != Hasher.GetHashCode()) - { + if (ReferenceEquals(obj, this)) + return true; + if (obj == null) return false; - } - // NH-3957: do not rely on hashcode alone. - // Must be the exact same type - return obj.GetType() == typeof(AliasToEntityMapResultTransformer); + return obj.GetType() == GetType(); } public override int GetHashCode() { - return Hasher.GetHashCode(); + return System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(Instance); } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Transform/CacheableResultTransformer.cs b/src/NHibernate/Transform/CacheableResultTransformer.cs index 91ff637aba0..1174f5d9478 100644 --- a/src/NHibernate/Transform/CacheableResultTransformer.cs +++ b/src/NHibernate/Transform/CacheableResultTransformer.cs @@ -19,7 +19,7 @@ public class CacheableResultTransformer : IResultTransformer // is private (as it should be for a singleton) //private const PassThroughResultTransformer ACTUAL_TRANSFORMER = // PassThroughResultTransformer.INSTANCE; - private readonly PassThroughResultTransformer _actualTransformer = new PassThroughResultTransformer(); + private readonly PassThroughResultTransformer _actualTransformer = PassThroughResultTransformer.Instance; public bool AutoDiscoverTypes { get; } @@ -358,7 +358,7 @@ public override bool Equals(object o) if (this == o) return true; - if (o == null || typeof (CacheableResultTransformer) != o.GetType()) + if (o == null || GetType() != o.GetType()) return false; var that = (CacheableResultTransformer) o; diff --git a/src/NHibernate/Transform/DistinctRootEntityResultTransformer.cs b/src/NHibernate/Transform/DistinctRootEntityResultTransformer.cs index c4a65ce87cb..6cd4211bab1 100644 --- a/src/NHibernate/Transform/DistinctRootEntityResultTransformer.cs +++ b/src/NHibernate/Transform/DistinctRootEntityResultTransformer.cs @@ -10,7 +10,7 @@ namespace NHibernate.Transform public class DistinctRootEntityResultTransformer : IResultTransformer, ITupleSubsetResultTransformer { private static readonly INHibernateLogger log = NHibernateLogger.For(typeof(DistinctRootEntityResultTransformer)); - private static readonly object Hasher = new object(); + internal static readonly DistinctRootEntityResultTransformer Instance = new DistinctRootEntityResultTransformer(); internal sealed class Identity { @@ -62,33 +62,26 @@ public IList TransformList(IList list) public bool[] IncludeInTransform(String[] aliases, int tupleLength) { - //return RootEntityResultTransformer.INSTANCE.includeInTransform(aliases, tupleLength); - var transformer = new RootEntityResultTransformer(); - return transformer.IncludeInTransform(aliases, tupleLength); + return RootEntityResultTransformer.Instance.IncludeInTransform(aliases, tupleLength); } - public bool IsTransformedValueATupleElement(String[] aliases, int tupleLength) { - //return RootEntityResultTransformer.INSTANCE.isTransformedValueATupleElement(null, tupleLength); - var transformer = new RootEntityResultTransformer(); - return transformer.IsTransformedValueATupleElement(null, tupleLength); + return RootEntityResultTransformer.Instance.IsTransformedValueATupleElement(null, tupleLength); } public override bool Equals(object obj) { - if (obj == null || obj.GetHashCode() != Hasher.GetHashCode()) - { + if (ReferenceEquals(obj, this)) + return true; + if (obj == null) return false; - } - // NH-3957: do not rely on hashcode alone. - // Must be the exact same type - return obj.GetType() == typeof(DistinctRootEntityResultTransformer); + return obj.GetType() == GetType(); } public override int GetHashCode() { - return Hasher.GetHashCode(); + return RuntimeHelpers.GetHashCode(Instance); } } } diff --git a/src/NHibernate/Transform/PassThroughResultTransformer.cs b/src/NHibernate/Transform/PassThroughResultTransformer.cs index 0a116b51aff..72454e2883a 100644 --- a/src/NHibernate/Transform/PassThroughResultTransformer.cs +++ b/src/NHibernate/Transform/PassThroughResultTransformer.cs @@ -7,7 +7,7 @@ namespace NHibernate.Transform [Serializable] public class PassThroughResultTransformer : IResultTransformer, ITupleSubsetResultTransformer { - private static readonly object Hasher = new object(); + internal static readonly PassThroughResultTransformer Instance = new PassThroughResultTransformer(); #region IResultTransformer Members @@ -58,21 +58,18 @@ internal object[] UntransformToTuple(object transformed, bool isSingleResult) return isSingleResult ? new[] {transformed} : (object[]) transformed; } - public override bool Equals(object obj) { - if (obj == null || obj.GetHashCode() != Hasher.GetHashCode()) - { + if (ReferenceEquals(obj, this)) + return true; + if (obj == null) return false; - } - // NH-3957: do not rely on hashcode alone. - // Must be the exact same type - return obj.GetType() == typeof(PassThroughResultTransformer); + return obj.GetType() == GetType(); } public override int GetHashCode() { - return Hasher.GetHashCode(); + return System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(Instance); } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Transform/RootEntityResultTransformer.cs b/src/NHibernate/Transform/RootEntityResultTransformer.cs index b8468ebbc0c..5d2ecc9b13f 100644 --- a/src/NHibernate/Transform/RootEntityResultTransformer.cs +++ b/src/NHibernate/Transform/RootEntityResultTransformer.cs @@ -7,7 +7,7 @@ namespace NHibernate.Transform [Serializable] public class RootEntityResultTransformer : IResultTransformer, ITupleSubsetResultTransformer { - private static readonly object Hasher = new object(); + internal static readonly RootEntityResultTransformer Instance = new RootEntityResultTransformer(); public object TransformTuple(object[] tuple, string[] aliases) { @@ -19,13 +19,11 @@ public IList TransformList(IList collection) return collection; } - public bool IsTransformedValueATupleElement(String[] aliases, int tupleLength) { return true; } - public bool[] IncludeInTransform(String[] aliases, int tupleLength) { bool[] includeInTransform; @@ -43,18 +41,16 @@ public bool[] IncludeInTransform(String[] aliases, int tupleLength) public override bool Equals(object obj) { - if (obj == null || obj.GetHashCode() != Hasher.GetHashCode()) - { + if (ReferenceEquals(obj, this)) + return true; + if (obj == null) return false; - } - // NH-3957: do not rely on hashcode alone. - // Must be the exact same type - return obj.GetType() == typeof(RootEntityResultTransformer); + return obj.GetType() == GetType(); } public override int GetHashCode() { - return Hasher.GetHashCode(); + return System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(Instance); } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Transform/ToListResultTransformer.cs b/src/NHibernate/Transform/ToListResultTransformer.cs index e6ccc1977bd..03d6b93324c 100644 --- a/src/NHibernate/Transform/ToListResultTransformer.cs +++ b/src/NHibernate/Transform/ToListResultTransformer.cs @@ -11,7 +11,7 @@ namespace NHibernate.Transform [Serializable] public class ToListResultTransformer : IResultTransformer { - private static readonly object Hasher = new object(); + internal static readonly ToListResultTransformer Instance = new ToListResultTransformer(); public object TransformTuple(object[] tuple, string[] aliases) { @@ -25,18 +25,16 @@ public IList TransformList(IList list) public override bool Equals(object obj) { - if (obj == null || obj.GetHashCode() != Hasher.GetHashCode()) - { + if (ReferenceEquals(obj, this)) + return true; + if (obj == null) return false; - } - // NH-3957: do not rely on hashcode alone. - // Must be the exact same type - return obj.GetType() == typeof(ToListResultTransformer); + return obj.GetType() == GetType(); } public override int GetHashCode() { - return Hasher.GetHashCode(); + return System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(Instance); } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Transform/Transformers.cs b/src/NHibernate/Transform/Transformers.cs index 92755f94db2..e139c709e8f 100644 --- a/src/NHibernate/Transform/Transformers.cs +++ b/src/NHibernate/Transform/Transformers.cs @@ -7,10 +7,10 @@ public static class Transformers /// /// Each row of results is a map () from alias to values/entities /// - public static readonly IResultTransformer AliasToEntityMap = new AliasToEntityMapResultTransformer(); + public static readonly IResultTransformer AliasToEntityMap = AliasToEntityMapResultTransformer.Instance; /// Each row of results is a - public static readonly ToListResultTransformer ToList = new ToListResultTransformer(); + public static readonly ToListResultTransformer ToList = ToListResultTransformer.Instance; /// /// Creates a result transformer that will inject aliased values into instances @@ -44,15 +44,15 @@ public static IResultTransformer AliasToBean() return AliasToBean(typeof(T)); } - public static readonly IResultTransformer DistinctRootEntity = new DistinctRootEntityResultTransformer(); + public static readonly IResultTransformer DistinctRootEntity = DistinctRootEntityResultTransformer.Instance; public static IResultTransformer AliasToBeanConstructor(System.Reflection.ConstructorInfo constructor) { return new AliasToBeanConstructorResultTransformer(constructor); } - public static readonly IResultTransformer PassThrough = new PassThroughResultTransformer(); + public static readonly IResultTransformer PassThrough = PassThroughResultTransformer.Instance; - public static readonly IResultTransformer RootEntity = new RootEntityResultTransformer(); + public static readonly IResultTransformer RootEntity = RootEntityResultTransformer.Instance; } }