I have tried many of the examples I found on the internet. I wrote my own. This takes into account primitives, non-primitives (ex.Colors, string), child objects, and Enumerated lists.
The code, below, results in a list of Variance objects that contain:
Variance {
Path : WordSearchPuzzlesProject.Puzzle.Name
Prop : Name
X Value: Changed the Name
Y Value:
}
And, now, for the code…
using System;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.Reflection;
namespace ReflectionBasedTools
{
/// <summary>
///
/// </summary>
/// <typeparam name="T"></typeparam>
public class ReflectionComparer<T>
{
private string _message = string.Empty;
private int _interrogationCnt = 0;
private List<Variance> _variances = new List<Variance>();
/// <summary>
/// Accessor Mutator for the _variances member of type Variance[]
/// </summary>
public Variance[] Variances
{
get { return _variances.ToArray(); }
}
/// <summary>
/// Accessor Mutator for the Matched member of type <class>
/// </summary>
public bool Matched
{
get { return _variances.Count == 0 && _interrogationCnt > 1; }
}
/// <summary>
/// Property for the _interrogationCnt member of type int
///
/// </summary>
public int InterrogationCnt
{
get { return _interrogationCnt; }
}
/// <summary>
/// This method adds to the class message member.
/// It returns false so that, after adding to the
/// message member, it can be used as a return value
/// from the methods from which this method is called.
/// Ex:
/// public bool MethodName(string mustNotBeNull)
/// {
/// if(mustNotBeNull == null)
/// return AddToMessage(2103261437, "Must Not Be Null... is.");
///
/// return true;
/// }
/// </summary>
/// <param name="id"></param>
/// <param name="newMessage"></param>
/// <returns></returns>
public bool AddToMessage(int id, string newMessage)
{
if (_message.Trim().Length > 0)
_message += "\n";
_message += "(" + id + ")";
if(newMessage != null)
_message += newMessage;
return false;
}
/// <summary>
///
/// </summary>
public void Clear()
{
Clear();
_interrogationCnt = 0;
_variances.Clear();
}
/// <summary>
/// The entry point for the comparison. This method calls a recursive method
/// that reads the properties of all child objects (hopefully).
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
public bool Comparer(T x, T y)
{
if (x == null || y == null)
return AddToMessage(1155186051, "Both x and y objects must be provided.");
bool result = CompareRecurive(x, y, x.GetType().Name);
return true;
}
/// <summary>
///
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
private bool CompareRecurive(object x, object y, string path)
{
_interrogationCnt++;
if (x == null)
{
// The same object should not be passed for both x and y
return true;
}
if (x == null || y == null)
return true;
if ((x == null && y != null) || (x != null && y == null))
{
string name = x != null ? x.GetType().Name : y.GetType().Name;
_variances.Add(
new Variance(
"null/not-null: " + name,
x != null ? x : "[x is null]",
y != null ? y : "[y is null]",
path
)
);
return true;
}
PropertyInfo[] xProps = x.GetType().GetProperties();
PropertyInfo[] yProps = y.GetType().GetProperties();
Dictionary<string, PropertyInfo> yDict = new Dictionary<string, PropertyInfo>();
if (!(x is IEnumerable))
{
for (int i = 0; i < yProps.Length; i++)
{
yDict.Add(yProps[i].Name, yProps[i]);
}
}
else
{
int xCount = CountIEnumerable((IEnumerable)x);
int yCount = CountIEnumerable((IEnumerable)y);
if (xCount != yCount)
{
AddToPath(ref path, x.GetType().Name);
_variances.Add(
new Variance(
x.GetType().Name + "(IEnumerable)",
"Count: " + xCount,
"Count: " + yCount,
path
)
);
RemoveFromPath(ref path);
}
else
{
List<object> xObjects = EnumerableObjects((IEnumerable)x);
List<object> yObjects = EnumerableObjects((IEnumerable)y);
for (int i = 0; i < xObjects.Count; i++)
{
object xObj = xObjects[i];
object yObj = yObjects[i];
// This is an item in the list, so the path has not changed.
CompareRecurive(xObj, yObj, path);
}
}
return true;
}
PropertyInfo yProp;
object xValue;
object yValue;
foreach (PropertyInfo xProp in xProps)
{
yProp = yDict[xProp.Name];
if (xProp.PropertyType.IsPrimitive)
{
xValue = xProp.GetValue(x, null);
yValue = yProp.GetValue(y, null);
bool reject = CheckNulls(xValue, yValue);
if (reject)
{
AddToPath(ref path, xProp.Name);
Variance variance =
new Variance(
xProp.Name,
xValue,
yValue,
path + "." + xProp.Name
);
_variances.Add(variance);
RemoveFromPath(ref path);
}
}
else if (yProp.PropertyType == typeof(string))
{
string xString = (string)xProp.GetValue(x, null);
string yString = (string)yProp.GetValue(y, null);
if (xString.CompareTo(yString) != 0)
{
AddToPath(ref path, xProp.Name);
Variance variance =
new Variance(
xProp.Name,
xString,
yString,
path
);
_variances.Add(variance);
RemoveFromPath(ref path);
}
}
else
{
try
{
if(xProp.PropertyType == typeof(Color))
{
Color xColor = (Color)xProp.GetValue(x);
Color yColor = (Color)yProp.GetValue(y);
if (!CompareColor(xColor, yColor))
{
AddToPath(ref path, xProp.Name);
_variances.Add(
new Variance(
xProp.Name,
xColor,
yColor,
path
)
);
RemoveFromPath(ref path);
}
return true;
}
AddToPath(ref path, xProp.Name);
CompareRecurive(
xProp.GetValue(x),
yProp.GetValue(y),
path
);
RemoveFromPath(ref path);
}
catch
{
}
}
}
return true;
}
/// <summary>
///
/// </summary>
/// <param name="path"></param>
/// <exception cref="NotImplementedException"></exception>
private void RemoveFromPath(ref string path)
{
int idx = path.LastIndexOf('.');
if (idx > -1)
path = path.Substring(0, idx);
}
/// <summary>
///
/// </summary>
/// <param name="path"></param>
/// <param name="name"></param>
private void AddToPath(ref string path, string name)
{
if (!string.IsNullOrEmpty(name?.Trim()))
path += "." + name;
}
/// <summary>
/// The x and y may be null or objects,
/// but not one and object and the other a null.
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
private bool CheckNulls(object x, object y)
{
return
(
(
(x != null || y != null)
&& (
((x == null) && (y != null))
|| ((y != null) && (y == null))
)
)
||
(
x != null && x != null
&& x.ToString().CompareTo(y.ToString()) != 0
)
);
}
/// <summary>
///
/// </summary>
/// <param name="v1"></param>
/// <param name="v2"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
private bool CompareColor(Color color1, Color color2)
{
return
color1.A == color2.A
&& color1.R == color2.R
&& color1.G == color2.G
&& color1.B == color2.B
;
}
/// <summary>
///
/// </summary>
/// <param name="e"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
private List<object> EnumerableObjects(IEnumerable e)
{
List<object> result = new List<object>();
foreach(object obj in e)
result.Add(obj);
return result;
}
/// <summary>
/// Iterates of the items in the IEnumerable x.
/// Returns the count.
/// </summary>
/// <param name="x"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
private int CountIEnumerable(IEnumerable e)
{
int result = 0;
foreach (object obj in e)
{
result++;
}
return result;
}
}
/// <summary>
///
/// </summary>
[Serializable]
public class Variance
{
private string _prop = "";
private object _yValue = null;
private object _xValue = null;
private string _path = string.Empty;
/// <summary>
///
/// </summary>
public Variance()
{
}
/// <summary>
///
/// </summary>
/// <param name="prop"></param>
/// <param name="x"></param>
/// <param name="y"></param>
public Variance(string prop, object xValue, object yValue, string path)
{
_path = path;
_prop = prop;
_xValue = xValue;
_yValue = yValue;
}
#region Properties
/// <summary>
/// Accessor Mutator for the _path member of type String
/// </summary>
public String Path
{
get { return _path; }
}
/// <summary>
/// Accessor Mutator for the prop member of type String
/// </summary>
public String Prop
{
get { return _prop; }
}
/// <summary>
/// Accessor Mutator for the xValue member of type object
/// </summary>
public object XValue
{
get { return _xValue; }
}
/// <summary>
/// Accessor Mutator for the _yValue member of type object
/// </summary>
public object YValue
{
get { return _yValue; }
}
#endregion
/// <summary>
///
/// </summary>
/// <returns></returns>
public override string ToString()
{
return
"Variance {" +
" Path : " + _path +
" Prop : " + _prop +
" X Value: " + _xValue +
" Y Value: " + _yValue +
"}";
;
}
}
}