using System; using System.Collections.Generic; using System.Linq; namespace Swan { /// /// Provides a way for types that override /// to correctly generate a hash code from the actual status of an instance. /// CompositeHashCode must be used ONLY as a helper when implementing /// IEquatable<T> in a STANDARD way, i.e. when: /// /// two instances having the same hash code are actually /// interchangeable, i.e. they represent exactly the same object (for instance, /// they should not coexist in a /// SortedSet); /// GetHashCode and /// Equals are BOTH overridden, and the Equals /// override either calls to the IEquatable<T>.Equals /// (recommended) or performs exactly the same equality checks; /// only "standard" equality checks are performed, i.e. by means of the /// == operator, IEquatable<T> interfaces, and /// the Equals method (for instance, this excludes case-insensitive /// and/or culture-dependent string comparisons); /// /// the hash code is constructed (via Using calls) from the very same /// fields and / or properties that are checked for equality. /// /// For hashing to work correctly, all fields and/or properties involved in hashing must either /// be immutable, or at least not change while an object is referenced in a hashtable. /// This does not refer just to System.Collections.Hashtable; the .NET /// Framework makes a fairly extensive use of hashing, for example in /// SortedSet<T> /// and in various parts of LINQ. As a thumb rule, an object must stay the same during the execution of a /// LINQ query on an IEnumerable /// in which it is contained, as well as all the time it is referenced in a Hashtable or SortedSet. /// /// /// The following code constitutes a minimal use case for CompositeHashCode, as well /// as a reference for standard IEquatable<T> implementation. /// Notice that all relevant properties are immutable; this is not, as stated in the summary, /// an absolute requirement, but it surely helps and should be done every time it makes sense. /// using System; /// using Swan; /// /// namespace Example /// { /// public class Person : IEquatable<Person> /// { /// public string Name { get; private set; } /// /// public int Age { get; private set; } /// /// public Person(string name, int age) /// { /// Name = name; /// Age = age; /// } /// /// public override int GetHashCode() => CompositeHashCode.Using(Name, Age); /// /// public override bool Equals(object obj) => obj is Person other && Equals(other); /// /// public bool Equals(Person other) /// => other != null /// && other.Name == Name /// && other.Age == Age; /// } /// } /// public static class CompositeHashCode { #region Private constants private const int InitialSeed = 17; private const int Multiplier = 29; #endregion #region Public API /// /// Computes a hash code, taking into consideration the values of the specified /// fields and/oror properties as part of an object's state. See the /// example. /// /// The values of the fields and/or properties. /// The computed has code. public static int Using(params object[] fields) { unchecked { return fields.Where(f => !(f is null)) .Aggregate(InitialSeed, (current, field) => (Multiplier * current) + field.GetHashCode()); } } #endregion } }