Skip to content

Instantly share code, notes, and snippets.

@neuecc
Last active January 21, 2020 19:02
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save neuecc/082a0152877c393e58735c7d66af3850 to your computer and use it in GitHub Desktop.
Save neuecc/082a0152877c393e58735c7d66af3850 to your computer and use it in GitHub Desktop.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
namespace Cysharp.Reflection
{
public static class DiffUtil
{
public static DifferentValue[] GetDiffResult<T>(T left, T right, bool useFullName = false)
{
var result = new List<DifferentValue>();
StructuralEqual(left, right, new[] { useFullName ? typeof(T).FullName : typeof(T).Name }, ref result);
return result.ToArray();
}
public static string DumpDiffResult<T>(T left, T right, bool useFullName = false)
{
var result = GetDiffResult(left, right, useFullName);
if (result.Length == 0)
{
return "No diff found.";
}
else
{
var sb = new StringBuilder();
foreach (var item in result)
{
sb.AppendLine($"{string.Join(".", item.Names)} Left:{item.Left?.ToString()} Right:{item.Right?.ToString()}");
}
return sb.ToString();
}
}
static object GetValue(MemberInfo memberInfo, object value)
{
if (memberInfo is PropertyInfo pi)
{
return pi.GetGetMethod(true).Invoke(value, null);
}
else if (memberInfo is FieldInfo fi)
{
return fi.GetValue(value);
}
throw new ArgumentException();
}
static void StructuralEqual(object left, object right, IEnumerable<string> names, ref List<DifferentValue> context)
{
if (object.ReferenceEquals(left, right)) return;
if (left == null || right == null)
{
context.Add(new DifferentValue { Left = left, Right = right, Names = names });
return;
}
var lType = left.GetType();
var rType = right.GetType();
if (lType != rType)
{
context.Add(new DifferentValue { Left = left, Right = right, Names = names });
return;
}
var type = left.GetType();
// not object(int, string, etc...)
if (Type.GetTypeCode(type) != TypeCode.Object)
{
var same = left.Equals(right);
if (!same)
{
context.Add(new DifferentValue { Left = left, Right = right, Names = names });
}
return;
}
// is sequence
if (typeof(IEnumerable).IsAssignableFrom(type))
{
SequenceEqual((IEnumerable)left, (IEnumerable)right, names, ref context);
return;
}
// IEquatable<T>
var equatable = typeof(IEquatable<>).MakeGenericType(type);
if (equatable.IsAssignableFrom(type))
{
var result = (bool)equatable.GetMethod("Equals").Invoke(left, new[] { right });
if (!result)
{
context.Add(new DifferentValue { Left = left, Right = right, Names = names });
}
return;
}
// is object
ObjectEqual(left, right, names, ref context);
}
static void ObjectEqual(object left, object right, IEnumerable<string> names, ref List<DifferentValue> context)
{
var fields = left.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public);
var properties = left.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public).Where(x => x.GetGetMethod(false) != null);
var members = fields.Cast<MemberInfo>().Concat(properties);
foreach (var mi in fields.Cast<MemberInfo>().Concat(properties))
{
var concatNames = names.Concat(new[] { (string)mi.Name });
object lv = GetValue(mi, left);
object rv = GetValue(mi, right);
StructuralEqual(lv, rv, concatNames, ref context);
}
}
static void SequenceEqual(IEnumerable leftEnumerable, IEnumerable rightEnumarable, IEnumerable<string> names, ref List<DifferentValue> context)
{
var le = leftEnumerable.GetEnumerator();
using (le as IDisposable)
{
var re = rightEnumarable.GetEnumerator();
using (re as IDisposable)
{
var index = 0;
while (true)
{
object lValue = null;
object rValue = null;
var lMove = le.MoveNext();
var rMove = re.MoveNext();
if (lMove) lValue = le.Current;
if (rMove) rValue = re.Current;
if (lMove && rMove)
{
StructuralEqual(lValue, rValue, names.Concat(new[] { "[" + index + "]" }), ref context);
}
if ((lMove == true && rMove == false) || (lMove == false && rMove == true))
{
var diff = new DifferentValue { Left = lValue, Right = rValue, Names = names.Concat(new[] { "[" + index + "]" }) };
context.Add(diff);
return;
}
if (lMove == false && rMove == false) break;
index++;
}
}
}
}
}
public class DifferentValue
{
public object Left;
public object Right;
public IEnumerable<string> Names;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment