I am working on a method that can pass in 2 objects of the same type and it will compare the two. In the end I am planning on overloading it to accept property names etc to evaluate only those or exclude only those form the evaluation. That will have to happen after I get the basic full compare working.
I have be been able to compare value types and reference types. Where I am stuck is being able to compare properties that are Enumerable. The else
statement is what I cannot get to work correctly.
public static void CompareObjects<T>(T obj1, T obj2)
{
foreach (var property in typeof(T).GetProperties())
{
var obj1Prop = property.GetValue(obj1);
var obj2Prop = property.GetValue(obj2);
//for value types
if (property.PropertyType.IsPrimitive || property.PropertyType.IsValueType || property.PropertyType == typeof(string))
{
if (obj1Prop != obj2Prop)
Console.WriteLine($"Object 1 {property.Name}:{obj1Prop}{Environment.NewLine}Object 2 {property.Name}:{obj2Prop}{Environment.NewLine}");
}
//for objects
else if (property.PropertyType.IsClass && !typeof(IEnumerable).IsAssignableFrom(property.PropertyType))
{
dynamic obj1PropObj = Convert.ChangeType(obj1Prop, property.PropertyType);
dynamic obj2PropObj = Convert.ChangeType(obj2Prop, property.PropertyType);
CompareObjects(obj1PropObj, obj2PropObj);
}
//for Enumerables
else
{
var enumerablePropObj1 = property.GetValue(obj1) as IEnumerable;
var enumerablePropObj2 = property.GetValue(obj2) as IEnumerable;
if (enumerablePropObj1 == null) continue;
if (enumerablePropObj2 == null) continue;
var list1 = enumerablePropObj1.GetEnumerator();
var list2 = enumerablePropObj2.GetEnumerator();
while (list1.MoveNext())
{
list2.MoveNext();
CompareObjects(list1.Current, list2.Current);
}
}
}
}
This iterates through the list but without the values. The setup that I am using to implement this is as follows:
static void Main(string[] args)
{
var student1 = new Student
{
FirstName = "John",
LastName = "Smith",
Age = 18,
DateOfBirth = DateTime.Parse("1989/03/03"),
Job = new Job
{
Company = "CompanyOne",
Title = "Supervisor"
},
PhoneNumbers = new List<PhoneNumber>
{
new PhoneNumber
{
Number = "8675309",
Label = "Home"
},
new PhoneNumber
{
Number = "1234567",
Label = "Work"
}
}
};
var student2 = new Student
{
FirstName = "Theodore",
LastName = "Smith",
Age = 22,
DateOfBirth = DateTime.Parse("1990/03/03"),
Job = new Job
{
Company = "CompanyOne",
Title = "Manager"
},
PhoneNumbers = new List<PhoneNumber>
{
new PhoneNumber
{
Number = "8675308",
Label = "Home"
},
new PhoneNumber
{
Number = "1234567",
Label = "Work"
}
}
};
CompareObjects(student1, student2);
Console.WriteLine("Done");
Console.ReadKey();
}
Your problem is that your method is generic. When you call it on the sub-collections, the type you are using for the comparison is System.Object
, which of course has no interesting properties to compare.
Change your method declaration and the preamble of the method:
public static void CompareObjects(object obj1, object obj2)
{
if (obj1.GetType() != obj2.GetType())
{
return;
}
foreach (var property in obj1.GetType().GetProperties())
{
...
Note that with this approach, you also do not need the dynamic
and ChangeType()
code.
Finally, you may want to consider changing your inequality comparison so that instead of using the !=
operator it uses the Equals()
method:
if (!obj1Prop.Equals(obj2Prop))
{
Console.WriteLine($"Object 1 {property.Name}:{obj1Prop}{Environment.NewLine}Object 2 {property.Name}:{obj2Prop}{Environment.NewLine}");
}
Even when the method was generic, that would be a good idea; while one is supposed to implement the equality and inequality operators if one overrides Equals()
, that doesn't always happen. When the method is non-generic, of course you won't even get operator overloads for the specific types (at least not without a lot of extra work).
(I guess you could instead of the above use the same dynamic
/ChangeType()
approach you use for the non-enumerable properties, using e.g. list1.Current.GetType()
as the type instead of property.PropertyType
; that could allow the method to remain generic. But there's so much extra overhead to dynamic
and ultimately it will all come back down to executing the same code, I just don't see the point in doing it that way.)