I have these data transfer objects:
public class Report
{
public int Id { get; set; }
public int ProjectId { get; set; }
//and so on for many, many properties.
}
I don't want to write
public bool areEqual(Report a, Report b)
{
if (a.Id != b.Id) return false;
if (a.ProjectId != b.ProjectId) return false;
//Repeat ad nauseum
return true;
}
Is there a faster way to test if two object with only properties have the same values (something that doesn't require one line of code or one logical expression per property?)
Switching to structs is not an option.
How about some reflection, perhaps using Expression.Compile()
for performance? (note the static ctor here ensures we only compile it once per T
):
using System;
using System.Linq.Expressions;
public class Report {
public int Id { get; set; }
public int ProjectId { get; set; }
static void Main() {
Report a = new Report { Id = 1, ProjectId = 13 },
b = new Report { Id = 1, ProjectId = 13 },
c = new Report { Id = 1, ProjectId = 12 };
Console.WriteLine(PropertyCompare.Equal(a, b));
Console.WriteLine(PropertyCompare.Equal(a, c));
}
}
static class PropertyCompare {
public static bool Equal<T>(T x, T y) {
return Cache<T>.Compare(x, y);
}
static class Cache<T> {
internal static readonly Func<T, T, bool> Compare;
static Cache() {
var props = typeof(T).GetProperties();
if (props.Length == 0) {
Compare = delegate { return true; };
return;
}
var x = Expression.Parameter(typeof(T), "x");
var y = Expression.Parameter(typeof(T), "y");
Expression body = null;
for (int i = 0; i < props.Length; i++) {
var propEqual = Expression.Equal(
Expression.Property(x, props[i]),
Expression.Property(y, props[i]));
if (body == null) {
body = propEqual;
} else {
body = Expression.AndAlso(body, propEqual);
}
}
Compare = Expression.Lambda<Func<T, T, bool>>(body, x, y)
.Compile();
}
}
}
Edit: updated to handle fields too:
static class MemberCompare
{
public static bool Equal<T>(T x, T y)
{
return Cache<T>.Compare(x, y);
}
static class Cache<T>
{
internal static readonly Func<T, T, bool> Compare;
static Cache()
{
var members = typeof(T).GetProperties(
BindingFlags.Instance | BindingFlags.Public)
.Cast<MemberInfo>().Concat(typeof(T).GetFields(
BindingFlags.Instance | BindingFlags.Public)
.Cast<MemberInfo>());
var x = Expression.Parameter(typeof(T), "x");
var y = Expression.Parameter(typeof(T), "y");
Expression body = null;
foreach(var member in members)
{
Expression memberEqual;
switch (member.MemberType)
{
case MemberTypes.Field:
memberEqual = Expression.Equal(
Expression.Field(x, (FieldInfo)member),
Expression.Field(y, (FieldInfo)member));
break;
case MemberTypes.Property:
memberEqual = Expression.Equal(
Expression.Property(x, (PropertyInfo)member),
Expression.Property(y, (PropertyInfo)member));
break;
default:
throw new NotSupportedException(
member.MemberType.ToString());
}
if (body == null)
{
body = memberEqual;
}
else
{
body = Expression.AndAlso(body, memberEqual);
}
}
if (body == null)
{
Compare = delegate { return true; };
}
else
{
Compare = Expression.Lambda<Func<T, T, bool>>(body, x, y)
.Compile();
}
}
}
}