Reputation: 2863
I have two classes Address and Employee as follows:
public class Address
{
public string AddressLine1 { get; set; }
public string AddressLine2 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Zip { get; set; }
}
public class Employee
{
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
public Address EmployeeAddress { get; set; }
}
I have two employee instances as follows:
var emp1Address = new Address();
emp1Address.AddressLine1 = "Microsoft Corporation";
emp1Address.AddressLine2 = "One Microsoft Way";
emp1Address.City = "Redmond";
emp1Address.State = "WA";
emp1Address.Zip = "98052-6399";
var emp1 = new Employee();
emp1.FirstName = "Bill";
emp1.LastName = "Gates";
emp1.EmployeeAddress = emp1Address;
var emp2Address = new Address();
emp2Address.AddressLine1 = "Gates Foundation";
emp2Address.AddressLine2 = "One Microsoft Way";
emp2Address.City = "Redmond";
emp2Address.State = "WA";
emp2Address.Zip = "98052-6399";
var emp2 = new Employee();
emp2.FirstName = "Melinda";
emp2.LastName = "Gates";
emp2.EmployeeAddress = emp2Address;
Now how can I write a method which compares these two employees and returns the list of properties which have different values. So in this example I would like the result to be FirstName and Address.AddressLine1 .
Upvotes: 14
Views: 30833
Reputation: 11
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 +
"}";
;
}
}
}
Upvotes: 0
Reputation: 21860
Like LBushskin said, you do not have to do this. This is not the fastest way! Buy if you want, try this:
public static List<PropertyInfo> GetDifferences(Employee test1, Employee test2)
{
List<PropertyInfo> differences = new List<PropertyInfo>();
foreach (PropertyInfo property in test1.GetType().GetProperties())
{
object value1 = property.GetValue(test1, null);
object value2 = property.GetValue(test2, null);
if (!value1.Equals(value2))
{
differences.Add(property);
}
}
return differences;
}
Upvotes: 30
Reputation: 486
Here is a generic and recursive solution based on Oskar Kjellin's awnser.
I have posted this code as gist as well, so you can check the latest version or star/clone/fork it :)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
protected List<KeyValuePair<Type, PropertyInfo>> RecrusiveReflectionCompare<T>(T first, T second)
where T : class
{
var differences = new List<KeyValuePair<Type, PropertyInfo>>();
var parentType = first.GetType();
void CompareObject(object obj1, object obj2, PropertyInfo info)
{
if (!obj1.Equals(obj2))
{
differences.Add(new KeyValuePair<Type, PropertyInfo>(parentType, info));
}
}
foreach (PropertyInfo property in parentType.GetProperties())
{
object value1 = property.GetValue(first, null);
object value2 = property.GetValue(second, null);
if (property.PropertyType == typeof(string))
{
if (string.IsNullOrEmpty(value1 as string) != string.IsNullOrEmpty(value2 as string))
{
CompareObject(value1, value2, property);
}
}
else if (property.PropertyType.IsPrimitive)
{
CompareObject(value1, value2, property);
}
else
{
if (value1 == null && value2 == null)
{
continue;
}
differences.Concat(RecrusiveReflectionCompare(value1, value2));
}
}
return differences;
}
Upvotes: 7
Reputation: 245399
No need for reflection. Of course, this example is returning a string with the property names...if you need the actual PropertyInfo
object, things would get a little more difficult, but not by much.
public static IEnumerable<string> DiffEmployees
(Employee one, Employee two)
{
if(one.FirstName != two.FirstName)
yield return "FirstName";
if(one.LastName != two.LastName)
yield return "LastName";
if(one.Address.AddressLine1 != two.Address.AddressLine1)
yield return "Address.AddressLine1";
// And so on.
}
Upvotes: -1
Reputation: 6142
public IEnumerable<PropertyInfo> GetNotEqualsProperties(Employee emp1, Employee emp2)
{
Type employeeType = typeof (Employee);
var properies = employeeType.GetProperties();
foreach (var property in properies)
if(!property.GetValue(emp1, null).Equals(property.GetValue(emp2, null))) //TODO: check for null
yield return property;
}
And for complex properties you have to override Equals method
public class Address
{
public string AddressLine1 { get; set; }
public string AddressLine2 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Zip { get; set; }
public override bool Equals(object obj)
{
if (obj as Address == null)
return false;
return ((Address) obj).AddressLine1.Equals(AddressLine1);
}
}
Upvotes: 0
Reputation: 131666
You don't necessarily need reflection to perform the comparison. You can write a comparer class that takes two instances of Employee or Address, and compares each field that should match. For any that don't match, you can add a string (or PropertyInfo
) element to some list to return to the caller.
Whether you return a PropertyInfo
, MemberInfo
, or just a string depends on what the caller needs to do with the result. If you actually need to visit the fields that contain differences, the PropertyInfo/MemberInfo
may be better - but to just report the differences a string is probaby sufficient.
The main value of reflection would be to write a general purpose object comparer that could take two instances of any kind of object and compare their public fields and properties. This helps avoid writing repetetive comparison code over and over - but that doesn't seem like the case you're in.
Upvotes: 9