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 + "}"; ; } } }