Search code examples
c#dynamicreflectionequalityiequalitycomparer

How do you create a dynamic equality implementation where you can pass in the property names to be compared?


Say I have an object Person with the properties below:

    public class Person
    {
        public int ID { get; set; }
        public int EmployeeNo { get; set; }
        public string JobDescription { get; set; }
        public string Code { get; set; }
    }

How would I dynamically check the equality of specific properties by name?

eg.

var dynamicEqualityComparer = RetrieveDynamicEqualityComparer("ID", "JobDescription");
var intersectedPersons = listOfPerson1.Intersect(listOfPerson2, dynamicEqualityComparer);

The above snippit would use the default linq intersect method using the dynamically generated equality comparison method which only compares the fields "ID" and "JobDescription".

I would assume that something like this would have been easy to find, but so far have not been able to locate anything of the sort.


Solution

  • The solution I came to is below:

    The equality comparer class looks like:

    public class CustomPropertyEqualityComparer<T>: IEqualityComparer<T> where T : class
    {
        private readonly string[] _selectedComparisonProperties;
    
        public CustomPropertyEqualityComparer(params string[] selectedComparisonProperties)
        {
            _selectedComparisonProperties = selectedComparisonProperties;
        }
    
        public bool Equals(T x, T y)
        {
            if (x != null && y != null && x.GetType() == y.GetType())
            {
                var type = x.GetType();
    
                var comparableProperties = new List<string>(_selectedComparisonProperties);
    
                var objectProperties = type.GetProperties();
    
                var relevantProperties = objectProperties.Where(propertyInfo => comparableProperties.Contains(propertyInfo.Name));
    
                foreach (var propertyInfo in relevantProperties)
                {
                    var xPropertyValue = type.GetProperty(propertyInfo.Name).GetValue(x, null);
    
                    var yPropertyValue = type.GetProperty(propertyInfo.Name).GetValue(y, null);
    
                    if (xPropertyValue != yPropertyValue && (xPropertyValue == null || !xPropertyValue.Equals(yPropertyValue)))
                    {
                        return false;
                    }
    
                }
                return true;
            }
            return x == y;
        }
    
        public int GetHashCode(T obj)
        {
            var type = typeof(T);
    
            var objectProperties = type.GetProperties();
    
            return objectProperties.Sum(property => property.GetHashCode());
        }
    }
    

    To create this class, you pass in a list of strings representing the objects property names.

    To call this class, I used the following bit of code:

    var groupKey = new List<string> {"EmployeeNo", "ID"}.ToArray();
    var customEqualityComparer = new CustomPropertyEqualityComparer<object>(groupKey);
    

    This creates a custom equality comparer for any class with the properties "EmployeeNo" and "ID".

    I used this comparer when checking if two tables contain the same entries where equality doesn't necessarily mean that every single field is equal..

    var existInBothTables = table1.Intersect(table2, customEqualityComparer).ToList();