2

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.

Ven
  • 19,015
  • 2
  • 41
  • 61
SamuelDavis
  • 3,312
  • 3
  • 17
  • 19
  • You can use reflection and expressions. For something similar, see http://stackoverflow.com/a/844855/1105687 and http://stackoverflow.com/a/986617/1105687 – lightbricko Jun 10 '13 at 01:08
  • Does it have to be through reflection? If you know it at compile time, why not make a generic `RetrieveDynamicEqualityComparer` that takes a `Func`? So for example, `var comparer = new RetrieveDynamicEqualityComparer((person1, person2) => person1.ID == person2.ID && person1.JobDescription == person2.JobDescription);`. Probably could even use `LambdaExpression` to make it a bit simpler by referring to the compile time properties. – Chris Sinclair Jun 10 '13 at 01:42
  • Don't have access to the class at compile time, the class is created through reflection. – SamuelDavis Jun 10 '13 at 01:50

2 Answers2

1

Put this in your person class then with your instance you can call equals

    public override bool Equals(object obj)
            {
                return obj.ToString() == this.ToString();
            }

           public override int GetHashCode()
           {
               return this.ToString().GetHashCode();
           }

            public override string ToString()
            {
                string myState;
                myState = string.Format("[ID: {0};EmployeeNo : {1}; JobDescription: {2};Code :{3}]",ID,EmployeeNo,JobDescription,Code);
                return myState;
            }

since the override of tostring accounts for all state data,in override equals you simply leverage your own implementation of ToString().

public int Compare(Person x, Person y)
        {
            if (x.ID == y.ID && x.JobDescription == y.JobDescription) 
                return 0;

            return (x.ID > y.ID) ? 1 : -1;//here you put what condition to return here i put ID just
                                          //for clarity,if u want just return -1 for ex:
        } 

this is the implementation of the IComparer<> interface of type Person

terrybozzio
  • 4,424
  • 1
  • 19
  • 25
  • That doesn't really answer my question unfortunately. In my example RetrieveDynamicEqualityComparer("ID", "JobDescription") I only want to check the two properties of "ID" and "JobDescription". The other properties may be populated with anything else and don't have to be equal. The equality should be determined by the two properties passed in. – SamuelDavis Jun 10 '13 at 01:17
  • Unfortunately your edit was not dynamic either. What I required was to have the parameters to be checked against to be passed in. I solved this problem. You can see my answer if you wish. – SamuelDavis Jun 10 '13 at 04:25
1

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();
SamuelDavis
  • 3,312
  • 3
  • 17
  • 19
  • This is acutally a real nice solution! One question, if you now make ListNew = ListA.Except(ListB, CustomEqualityComparer).ToList() and lets say ListA has duplicate Items, ListNew will only contain one of the duplicates, any idea how to keep the duplicate items? Thanks in advance! – kassi Feb 25 '15 at 14:27