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 /// /// /// public 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; } } } }