125 lines
3.0 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|