101 lines
2.7 KiB
C#
101 lines
2.7 KiB
C#
#nullable disable
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
|
|
namespace BizHawk.Common
|
|
{
|
|
/// <summary>Annotated fields will not be used by <see cref="DeepEquality"/> for comparison.</summary>
|
|
[AttributeUsage(AttributeTargets.Field)]
|
|
public sealed class DeepEqualsIgnoreAttribute : Attribute
|
|
{
|
|
}
|
|
|
|
public class DeepEquality
|
|
{
|
|
/// <summary>
|
|
/// return true if an array type is not 0-based
|
|
/// </summary>
|
|
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;
|
|
}
|
|
}
|
|
|
|
static MethodInfo ArrayEqualsGeneric = typeof(DeepEquality).GetMethod("ArrayEquals", BindingFlags.NonPublic | BindingFlags.Static);
|
|
|
|
/// <summary>test if two objects <paramref name="o1"/> and <paramref name="o2"/> are equal, field-by-field (with deep inspection of each field)</summary>
|
|
/// <exception cref="InvalidOperationException"><paramref name="o1"/> is an array with rank > 1 or is a non-zero-indexed array</exception>
|
|
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[] { 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;
|
|
}
|
|
}
|
|
}
|