Search code examples
c#.netlinqiequalitycomparer

Using custom EqualityComparer to check if C# item has been updated in a list


I am comparing 2 lists of objects using a custom comparer like so:

public class LocationEqualityComparer : IEqualityComparer<LocationData>
{
    public bool Equals(LocationData x, LocationData y)
    {
        var idComparer = string.Equals(x.Id, y.Id, 
            System.StringComparison.OrdinalIgnoreCase); 
        var nameComparer = string.Equals(x.Name, y.Name, 
            System.StringComparison.OrdinalIgnoreCase);
        var addressComparer = string.Equals(x.Address, y.Address, 
            System.StringComparison.OrdinalIgnoreCase);
        var postcodeComparer = string.Equals(x.PostCode, y.PostCode, 
            System.StringComparison.OrdinalIgnoreCase); 
     
        if (idComparer && nameComparer && addressComparer && postcodeComparer) 
        {
            return true; 
        }

        return false; 
    }
}

This works great for me when using Linq to check the equality using: If I have two lists of LocationData (previousRun and currentRun, I get the correct result with: List<LocationData> result = previousRun.Intersect(currentRun, new LocationEqualityComparer()).ToList();

I am also able to check which items have been added or deleted between the lists using Except in Linq.

What I want to be able to do is check if an item has been UPDATED between the lists. This is because they represent an old list (previous run) and a new list (current run). So for example the LocationData object will have the same Id, same address and same postcode but might have a slightly different name.

Does anyone know how I can get a list of objects that have been updated between lists (i.e. only one or maybe two properties have changed) but not defined as added or deleted?

Thank you


Solution

  • You could simply write a method that does the property comparison, but which returns true if a specific number of properties match (you said 1 or 2, so I guess it's variable?):

    public static bool IsUpdated(LocationData previous, LocationData current, 
        int numPropsToMatch = 2)
    {
        // If they are equal, return false
        if (new LocationEqualityComparer().Equals(previous, current)) return false;
    
        int numMatchingProps = 0;
    
        if (string.Equals(previous.Id, current.Id,
            System.StringComparison.OrdinalIgnoreCase)) numMatchingProps++;
        
        if (string.Equals(previous.Name, current.Name,
            System.StringComparison.OrdinalIgnoreCase)) numMatchingProps++;
        
        if (string.Equals(previous.Address, current.Address,
            System.StringComparison.OrdinalIgnoreCase)) numMatchingProps++;
        
        if (string.Equals(previous.PostCode, current.PostCode,
            System.StringComparison.OrdinalIgnoreCase)) numMatchingProps++;
    
        // Change to == if you *only* want a specific number to match
        return numMatchingProps >= numPropsToMatch;
    }
    

    Then you can just use this method in your Linq statement:

    List<LocationData> updated = currentRun
        .Where(curr => previousRun.Any(prev => IsUpdated(prev, curr)))
        .ToList();
    

    Note that it's highly likely that more than one location will have the same postal code, so that should probably not be included, but since it wasn't specified I left it.