using System.Reflection; using System.Diagnostics; using System; using System.Collections.Generic; using System.Linq; using Newtonsoft.Json; using System.Xml.Serialization; namespace SharpArch.Core.DomainModel { /// /// Provides a standard base class for facilitating comparison of objects. /// /// For a discussion of the implementation of Equals/GetHashCode, see /// http://devlicio.us/blogs/billy_mccafferty/archive/2007/04/25/using-equals-gethashcode-effectively.aspx /// and http://groups.google.com/group/sharp-architecture/browse_thread/thread/f76d1678e68e3ece?hl=en for /// an in depth and conclusive resolution. /// [Serializable] [JsonObject(MemberSerialization.OptIn)] public abstract class BaseObject { public override bool Equals(object obj) { BaseObject compareTo = obj as BaseObject; if (ReferenceEquals(this, compareTo)) return true; return compareTo != null && GetType().Equals(compareTo.GetTypeUnproxied()) && HasSameObjectSignatureAs(compareTo); } /// /// This is used to provide the hashcode identifier of an object using the signature /// properties of the object; although it's necessary for NHibernate's use, this can /// also be useful for business logic purposes and has been included in this base /// class, accordingly. Since it is recommended that GetHashCode change infrequently, /// if at all, in an object's lifetime, it's important that properties are carefully /// selected which truly represent the signature of an object. /// public override int GetHashCode() { unchecked { IEnumerable signatureProperties = GetSignatureProperties(); // It's possible for two objects to return the same hash code based on // identically valued properties, even if they're of two different types, // so we include the object's type in the hash calculation int hashCode = GetType().GetHashCode(); foreach (PropertyInfo property in signatureProperties) { object value = property.GetValue(this, null); if (value != null) hashCode = (hashCode * HASH_MULTIPLIER) ^ value.GetHashCode(); } if (signatureProperties.Any()) return hashCode; // If no properties were flagged as being part of the signature of the object, // then simply return the hashcode of the base object as the hashcode. return base.GetHashCode(); } } /// /// You may override this method to provide your own comparison routine. /// public virtual bool HasSameObjectSignatureAs(BaseObject compareTo) { IEnumerable signatureProperties = GetSignatureProperties(); foreach (PropertyInfo property in signatureProperties) { object valueOfThisObject = property.GetValue(this, null); object valueToCompareTo = property.GetValue(compareTo, null); if (valueOfThisObject == null && valueToCompareTo == null) continue; if ((valueOfThisObject == null ^ valueToCompareTo == null) || (!valueOfThisObject.Equals(valueToCompareTo))) { return false; } } // If we've gotten this far and signature properties were found, then we can // assume that everything matched; otherwise, if there were no signature // properties, then simply return the default bahavior of Equals return signatureProperties.Any() || base.Equals(compareTo); } /// /// public virtual IEnumerable GetSignatureProperties() { IEnumerable properties; // Init the signaturePropertiesDictionary here due to reasons described at // http://blogs.msdn.com/jfoscoding/archive/2006/07/18/670497.aspx if (signaturePropertiesDictionary == null) signaturePropertiesDictionary = new Dictionary>(); if (signaturePropertiesDictionary.TryGetValue(GetType(), out properties)) return properties; return (signaturePropertiesDictionary[GetType()] = GetTypeSpecificSignatureProperties()); } /// /// When NHibernate proxies objects, it masks the type of the actual entity object. /// This wrapper burrows into the proxied object to get its actual type. /// /// Although this assumes NHibernate is being used, it doesn't require any NHibernate /// related dependencies and has no bad side effects if NHibernate isn't being used. /// /// Related discussion is at http://groups.google.com/group/sharp-architecture/browse_thread/thread/ddd05f9baede023a ...thanks Jay Oliver! /// protected virtual Type GetTypeUnproxied() { return GetType(); } /// /// Enforces the template method pattern to have child objects determine which specific /// properties should and should not be included in the object signature comparison. Note /// that the the BaseObject already takes care of performance caching, so this method /// shouldn't worry about caching...just return the goods man! /// protected abstract IEnumerable GetTypeSpecificSignatureProperties(); /// /// This static member caches the domain signature properties to avoid looking them up for /// each instance of the same type. /// /// A description of the very slick ThreadStatic attribute may be found at /// http://www.dotnetjunkies.com/WebLog/chris.taylor/archive/2005/08/18/132026.aspx /// [ThreadStatic] private static Dictionary> signaturePropertiesDictionary; /// /// To help ensure hashcode uniqueness, a carefully selected random number multiplier /// is used within the calculation. Goodrich and Tamassia's Data Structures and /// Algorithms in Java asserts that 31, 33, 37, 39 and 41 will produce the fewest number /// of collissions. See http://computinglife.wordpress.com/2008/11/20/why-do-hash-functions-use-prime-numbers/ /// for more information. /// private const int HASH_MULTIPLIER = 31; } }