/*
* Copyright (c) 2008-2009 Markus Olsson
* var mail = string.Join(".", new string[] {"j", "markus", "olsson"}) + string.Concat('@', "gmail.com");
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this
* software and associated documentation files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
* BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
* TODO:
*
* - Infinite recursion protection (when an objects refers to an object which refer to the first object)
* by maintaining a thread static recursion protection list/set which uses ReferenceEquals for it's
* equality comparison.
*
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
namespace Tickster.Utils
{
///
/// Provides an implementation of EqualityComparer that performs memberwise
/// equality comparison of objects.
///
public class MemberwiseEqualityComparer : EqualityComparer
{
private static readonly Type targetType;
private static readonly FieldInfo[] targetFieldMembers;
private static readonly Func _equalityFunc;
private static readonly Func _hashCodeFunc;
///
/// Gets the default MemberwiseEqualityComparer for the type specified by the generic argument
///
public static new EqualityComparer Default
{
get { return new MemberwiseEqualityComparer(); }
}
static MemberwiseEqualityComparer()
{
targetType = typeof(T);
targetFieldMembers = targetType
.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.Where(fi => fi.GetCustomAttributes(typeof(MemberwiseEqualityIgnoreAttribute), true).Length == 0)
.ToArray();
_equalityFunc = BuildDynamicEqualityMethod();
_hashCodeFunc = BuildDynamicHashCodeMethod();
}
///
/// Initializes a new instance of the class.
///
public MemberwiseEqualityComparer()
{
}
///
/// When overridden in a derived class, determines whether two objects of type are equal.
///
/// The first object to compare.
/// The second object to compare.
///
/// true if the specified objects are equal; otherwise, false.
///
public override bool Equals(T x, T y)
{
if (ReferenceEquals(x, y))
return true;
return _equalityFunc(x, y);
}
///
/// Serves as a hash function for the specified object for hashing algorithms and data structures, such as a hash table.
///
/// The object to calculate a hash code for.
///
/// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
///
///
/// The type of is a reference type and is null.
///
public override int GetHashCode(T obj)
{
if (ReferenceEquals(obj, null))
return 0;
return _hashCodeFunc(obj);
}
///
/// Builds the dynamic hash code method.
///
private static Func BuildDynamicHashCodeMethod()
{
// If there's no members available for us to calculate hash code with we default to
// zero. Note that we cannot simply call the GetHashCode method of the T instance here
// since it very will lead to a never ending recursing if the T GetHashCode method calls
// MemberwiseEqualityComparer.Default.GetHashCode(this).
if (targetFieldMembers.Length == 0)
return x => 0;
var dynamicHashCodeMethod = new DynamicMethod("DynamicGetHashCode", typeof(int), new Type[] { targetType }, typeof(MemberwiseEqualityComparer), true);
ILGenerator il = dynamicHashCodeMethod.GetILGenerator();
il.Emit(OpCodes.Ldc_I4_0);
var typeHistory = new HashSet();
var equalityComparerGetters = new Dictionary();
var equalityComparerHashCodeMethods = new Dictionary();
foreach (FieldInfo fi in targetFieldMembers)
{
Type memberType = fi.FieldType;
MethodInfo defaultEqualityGetter;
MethodInfo equalityComparerHashCodeMethod;
if (!typeHistory.Contains(memberType))
{
typeHistory.Add(memberType);
Type genericEqualityComparer = typeof(EqualityComparer<>).MakeGenericType(new Type[] { memberType });
PropertyInfo defaultComparerProperty = genericEqualityComparer.GetProperty("Default", genericEqualityComparer);
defaultEqualityGetter = defaultComparerProperty.GetGetMethod();
equalityComparerHashCodeMethod = genericEqualityComparer.GetMethod("GetHashCode", new Type[] { memberType });
equalityComparerGetters.Add(memberType, defaultEqualityGetter);
equalityComparerHashCodeMethods.Add(memberType, equalityComparerHashCodeMethod);
}
else
{
defaultEqualityGetter = equalityComparerGetters[memberType];
equalityComparerHashCodeMethod = equalityComparerHashCodeMethods[memberType];
}
il.EmitCall(OpCodes.Call, defaultEqualityGetter, null);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, fi);
il.EmitCall(OpCodes.Callvirt, equalityComparerHashCodeMethod, null);
il.Emit(OpCodes.Xor);
}
il.Emit(OpCodes.Ret);
return (Func)dynamicHashCodeMethod.CreateDelegate(typeof(Func));
}
///
/// Builds the dynamic equality method.
///
private static Func BuildDynamicEqualityMethod()
{
// In case there are no fields we consider the objects to be equal
if (targetFieldMembers.Length == 0)
return (x, y) => true;
var equalityMethod = new DynamicMethod("DynamicEquals", typeof(bool), new Type[] { targetType, targetType }, typeof(MemberwiseEqualityComparer), true);
ILGenerator il = equalityMethod.GetILGenerator();
Label notEqualLabel = il.DefineLabel();
var typeHistory = new HashSet();
var equalityComparerGetters = new Dictionary();
var concreteEqualsMethods = new Dictionary();
MethodInfo referenceEquals = typeof(object).GetMethod("ReferenceEquals", new Type[] { typeof(object), typeof(object) });
foreach (FieldInfo fi in targetFieldMembers)
{
Type memberType = fi.FieldType;
MethodInfo propertyGetMethod;
MethodInfo concreteEqualsMethod;
if (!typeHistory.Contains(memberType))
{
typeHistory.Add(memberType);
Type genericEqualityComparer = typeof(EqualityComparer<>).MakeGenericType(new Type[] { memberType });
PropertyInfo defaultComparerProperty = genericEqualityComparer.GetProperty("Default", genericEqualityComparer);
propertyGetMethod = defaultComparerProperty.GetGetMethod();
concreteEqualsMethod = genericEqualityComparer.GetMethod("Equals", new Type[] { memberType, memberType });
equalityComparerGetters.Add(memberType, propertyGetMethod);
concreteEqualsMethods.Add(memberType, concreteEqualsMethod);
}
else
{
propertyGetMethod = equalityComparerGetters[memberType];
concreteEqualsMethod = concreteEqualsMethods[memberType];
}
Label skip = il.DefineLabel();
// Performance trick: Skip the real Equals() call on the EqualityProvider if where're dealing
// with the same object. Has the added benefit of making null comparisons really fast.
if (!memberType.IsValueType)
{
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, fi);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldfld, fi);
il.EmitCall(OpCodes.Call, referenceEquals, null);
il.Emit(OpCodes.Brtrue, skip);
}
il.EmitCall(OpCodes.Call, propertyGetMethod, null);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, fi);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldfld, fi);
il.EmitCall(OpCodes.Callvirt, concreteEqualsMethod, null);
il.Emit(OpCodes.Brfalse, notEqualLabel);
il.MarkLabel(skip);
}
il.Emit(OpCodes.Ldc_I4_1);
il.Emit(OpCodes.Ret);
il.MarkLabel(notEqualLabel);
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Ret);
return (Func)equalityMethod.CreateDelegate(typeof(Func));
}
}
}