RaspberryIO/Unosquare.Swan.Lite/Components/ObjectComparer.cs
2019-12-04 17:10:06 +01:00

183 lines
6.7 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Unosquare.Swan.Components {
/// <summary>
/// Represents a quick object comparer using the public properties of an object
/// or the public members in a structure.
/// </summary>
public static class ObjectComparer {
/// <summary>
/// Compare if two variables of the same type are equal.
/// </summary>
/// <typeparam name="T">The type of objects to compare.</typeparam>
/// <param name="left">The left.</param>
/// <param name="right">The right.</param>
/// <returns><c>true</c> if the variables are equal; otherwise, <c>false</c>.</returns>
public static Boolean AreEqual<T>(T left, T right) => AreEqual(left, right, typeof(T));
/// <summary>
/// Compare if two variables of the same type are equal.
/// </summary>
/// <param name="left">The left.</param>
/// <param name="right">The right.</param>
/// <param name="targetType">Type of the target.</param>
/// <returns>
/// <c>true</c> if the variables are equal; otherwise, <c>false</c>.
/// </returns>
/// <exception cref="ArgumentNullException">targetType.</exception>
public static Boolean AreEqual(Object left, Object right, Type targetType) {
if(targetType == null) {
throw new ArgumentNullException(nameof(targetType));
}
return Definitions.BasicTypesInfo.ContainsKey(targetType)
? Equals(left, right)
: targetType.IsValueType() || targetType.IsArray
? AreStructsEqual(left, right, targetType)
: AreObjectsEqual(left, right, targetType);
}
/// <summary>
/// Compare if two objects of the same type are equal.
/// </summary>
/// <typeparam name="T">The type of objects to compare.</typeparam>
/// <param name="left">The left.</param>
/// <param name="right">The right.</param>
/// <returns><c>true</c> if the objects are equal; otherwise, <c>false</c>.</returns>
public static Boolean AreObjectsEqual<T>(T left, T right)
where T : class => AreObjectsEqual(left, right, typeof(T));
/// <summary>
/// Compare if two objects of the same type are equal.
/// </summary>
/// <param name="left">The left.</param>
/// <param name="right">The right.</param>
/// <param name="targetType">Type of the target.</param>
/// <returns><c>true</c> if the objects are equal; otherwise, <c>false</c>.</returns>
/// <exception cref="ArgumentNullException">targetType.</exception>
public static Boolean AreObjectsEqual(Object left, Object right, Type targetType) {
if(targetType == null) {
throw new ArgumentNullException(nameof(targetType));
}
PropertyInfo[] properties = Runtime.PropertyTypeCache.RetrieveAllProperties(targetType).ToArray();
foreach(PropertyInfo propertyTarget in properties) {
Func<Object, Object> targetPropertyGetMethod = propertyTarget.GetCacheGetMethod();
if(propertyTarget.PropertyType.IsArray) {
IEnumerable leftObj = targetPropertyGetMethod(left) as IEnumerable;
IEnumerable rightObj = targetPropertyGetMethod(right) as IEnumerable;
if(!AreEnumerationsEquals(leftObj, rightObj)) {
return false;
}
} else {
if(!Equals(targetPropertyGetMethod(left), targetPropertyGetMethod(right))) {
return false;
}
}
}
return true;
}
/// <summary>
/// Compare if two structures of the same type are equal.
/// </summary>
/// <typeparam name="T">The type of structs to compare.</typeparam>
/// <param name="left">The left.</param>
/// <param name="right">The right.</param>
/// <returns><c>true</c> if the structs are equal; otherwise, <c>false</c>.</returns>
public static Boolean AreStructsEqual<T>(T left, T right)
where T : struct => AreStructsEqual(left, right, typeof(T));
/// <summary>
/// Compare if two structures of the same type are equal.
/// </summary>
/// <param name="left">The left.</param>
/// <param name="right">The right.</param>
/// <param name="targetType">Type of the target.</param>
/// <returns>
/// <c>true</c> if the structs are equal; otherwise, <c>false</c>.
/// </returns>
/// <exception cref="ArgumentNullException">targetType.</exception>
public static Boolean AreStructsEqual(Object left, Object right, Type targetType) {
if(targetType == null) {
throw new ArgumentNullException(nameof(targetType));
}
IEnumerable<MemberInfo> fields = new List<MemberInfo>(Runtime.FieldTypeCache.RetrieveAllFields(targetType))
.Union(Runtime.PropertyTypeCache.RetrieveAllProperties(targetType));
foreach(MemberInfo targetMember in fields) {
switch(targetMember) {
case FieldInfo field:
if(Equals(field.GetValue(left), field.GetValue(right)) == false) {
return false;
}
break;
case PropertyInfo property:
Func<Object, Object> targetPropertyGetMethod = property.GetCacheGetMethod();
if(targetPropertyGetMethod != null &&
!Equals(targetPropertyGetMethod(left), targetPropertyGetMethod(right))) {
return false;
}
break;
}
}
return true;
}
/// <summary>
/// Compare if two enumerables are equal.
/// </summary>
/// <typeparam name="T">The type of enums to compare.</typeparam>
/// <param name="left">The left.</param>
/// <param name="right">The right.</param>
/// <returns>
/// True if two specified types are equal; otherwise, false.
/// </returns>
/// <exception cref="ArgumentNullException">
/// left
/// or
/// right.
/// </exception>
public static Boolean AreEnumerationsEquals<T>(T left, T right)
where T : IEnumerable {
if(Equals(left, default(T))) {
throw new ArgumentNullException(nameof(left));
}
if(Equals(right, default(T))) {
throw new ArgumentNullException(nameof(right));
}
Object[] leftEnumerable = left.Cast<Object>().ToArray();
Object[] rightEnumerable = right.Cast<Object>().ToArray();
if(leftEnumerable.Length != rightEnumerable.Length) {
return false;
}
for(Int32 i = 0; i < leftEnumerable.Length; i++) {
Object leftEl = leftEnumerable[i];
Object rightEl = rightEnumerable[i];
if(!AreEqual(leftEl, rightEl, leftEl.GetType())) {
return false;
}
}
return true;
}
}
}