diff --git a/BizHawk.Common/DeepEquality.cs b/BizHawk.Common/DeepEquality.cs new file mode 100644 index 0000000000..820cd2f696 --- /dev/null +++ b/BizHawk.Common/DeepEquality.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Reflection; + +namespace BizHawk.Common +{ + /// + /// causes DeepEquality to ignore this field when determining equality + /// + [AttributeUsage(AttributeTargets.Field)] + public class DeepEqualsIgnoreAttribute : Attribute + { + } + + public class DeepEquality + { + /// + /// return true if an array type is not 0-based + /// + /// + /// + 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('*'); + } + + /// + /// return all instance fields of a type + /// + /// + /// + private static IEnumerable 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; + } + } + + /// + /// test if two arrays are equal in contents; arrays should have same type + /// + /// + /// + /// + /// + private static bool ArrayEquals(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); + + /// + /// test if two objects are equal field by field (with deep inspection of each field) + /// + /// + /// + /// + 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 }); + } + else if (t1.IsPrimitive) + { + return o1.Equals(o2); + } + else + { + foreach (var field in GetAllFields(t1)) + { + if (!DeepEquals(field.GetValue(o1), field.GetValue(o2))) + return false; + } + return true; + } + } + + } +}