BizHawk/BizHawk.Common/DeepEquality.cs

125 lines
3.0 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
namespace BizHawk.Common
{
/// <summary>
/// causes DeepEquality to ignore this field when determining equality
/// </summary>
[AttributeUsage(AttributeTargets.Field)]
public class DeepEqualsIgnoreAttribute : Attribute
{
}
public class DeepEquality
{
/// <summary>
/// return true if an array type is not 0-based
/// </summary>
/// <param name="t"></param>
private static bool IsNonZeroBaedArray(Type t)
{
if (!t.IsArray)
{
throw new InvalidOperationException();
}
// is there a better way to do this? i couldn't find any documentation...
return t.ToString().Contains('*');
}
/// <summary>
/// return all instance fields of a type
/// </summary>
public static IEnumerable<FieldInfo> GetAllFields(Type t)
{
while (t != null)
{
// GetFields() will not return inherited private fields, so walk the inheritance hierachy
foreach (var f in t.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.DeclaredOnly))
{
if (f.GetCustomAttributes(typeof(DeepEqualsIgnoreAttribute), true).Length == 0)
{
yield return f;
}
}
t = t.BaseType;
}
}
/// <summary>
/// test if two arrays are equal in contents; arrays should have same type
/// </summary>
private static bool ArrayEquals<T>(T[] o1, T[] o2)
{
if (o1.Length != o2.Length)
{
return false;
}
for (int i = 0; i < o1.Length; i++)
{
if (!DeepEquals(o1[i], o2[i]))
{
return false;
}
}
return true;
}
static MethodInfo ArrayEqualsGeneric = typeof(DeepEquality).GetMethod("ArrayEquals", BindingFlags.NonPublic | BindingFlags.Static);
/// <summary>
/// test if two objects are equal field by field (with deep inspection of each field)
/// </summary>
public static bool DeepEquals(object o1, object o2)
{
if (o1 == o2)
{
// reference equal, so nothing else to be done
return true;
}
Type t1 = o1.GetType();
Type t2 = o2.GetType();
if (t1 != t2)
{
return false;
}
if (t1.IsArray)
{
// it's not too hard to support thesse if needed
if (t1.GetArrayRank() > 1 || IsNonZeroBaedArray(t1))
{
throw new InvalidOperationException("Complex arrays not supported");
}
// this is actually pretty fast; it allows using fast ldelem and stelem opcodes on
// arbitrary array types without emitting custom IL
var method = ArrayEqualsGeneric.MakeGenericMethod(new Type[] { t1.GetElementType() });
return (bool)method.Invoke(null, new object[] { o1, o2 });
}
if (t1.IsPrimitive)
{
return o1.Equals(o2);
}
foreach (var field in GetAllFields(t1))
{
if (!DeepEquals(field.GetValue(o1), field.GetValue(o2)))
{
return false;
}
}
return true;
}
}
}