Использование настраиваемого EqualityComparer, чтобы проверить, обновлен ли элемент C# в списке - PullRequest
0 голосов
/ 04 августа 2020

Я сравниваю 2 списка объектов с помощью настраиваемого компаратора, например:

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; 
    }
}

Это отлично работает для меня, когда я использую Linq для проверки равенства, используя: previousRun и currentRun, я получаю правильный результат с: List<LocationData> result = previousRun.Intersect(currentRun, new LocationEqualityComparer()).ToList();

Я также могу проверить, какие элементы были добавлены или удалены между списками, используя Except в Linq.

Я хочу иметь возможность проверять, был ли элемент ОБНОВЛЕН между списками. Это потому, что они представляют старый список (предыдущий запуск) и новый список (текущий запуск). Так, например, объект LocationData будет иметь тот же идентификатор, адрес и почтовый индекс, но может иметь немного другое имя.

Кто-нибудь знает, как я могу получить список объектов, которые были обновлены между списками (т.е. только одно или, может быть, два свойства изменились) но не определены как добавленные или удаленные?

Спасибо

Ответы [ 3 ]

0 голосов
/ 04 августа 2020

Что ж, чтобы получить элементы, которые были обновлены между запусками, вам нужно написать новый IEqualityComparer для такого случая. В основном проверка того, что идентификатор остается таким же, как и был, но все остальное может быть изменено, например, имя, адрес и так далее. Вот пример такого компаратора с тестом - работает на моей стороне.

public class LocationIdEqualityComparer : IEqualityComparer<LocationData>
{
    public bool Equals(LocationData x, LocationData y)
    {
        bool idComparer = string.Equals(x.Id, y.Id,
            StringComparison.OrdinalIgnoreCase);
        bool nameComparer = string.Equals(x.Name, y.Name,
            StringComparison.OrdinalIgnoreCase);
        bool addressComparer = string.Equals(x.Address, y.Address,
            StringComparison.OrdinalIgnoreCase);
        bool postcodeComparer = string.Equals(x.PostCode, y.PostCode,
            StringComparison.OrdinalIgnoreCase);
        
        // so you need to check that ID is the same, but everything else may be different
        return idComparer && (!nameComparer || !addressComparer || !postcodeComparer);
    }

    public int GetHashCode(LocationData obj)
    {
        return obj.Id.GetHashCode();
    }
}

class TestUpdatedItemsInList
{
    [Test]
    public void TestItemsAreUpdated()
    {
        List<LocationData> originalList = new List<LocationData>
        {
            new LocationData("1", "first", "somewhere1", "postCode1"),
            new LocationData("2", "second", "somewhere2", "postCode2"),
            new LocationData("3", "third", "somewhere3", "postCode3"),
            new LocationData("4", "fourth", "somewhere4", "postCode4"),
        };

        List<LocationData> updatedList = new List<LocationData>
        {
            new LocationData("1", "1st", "somewhere1", "postCode1"),
            new LocationData("2", "second", "who knows where", "postCode2"),
            new LocationData("3", "third", "somewhere3", "updated postCode3"),
            new LocationData("4", "fourth", "somewhere4", "postCode4"),
            new LocationData("5", "fifth", "somewhere5", "postCode5"),
            new LocationData("6", "sixth", "somewhere6", "postCode6"),
        };
        
        // newly added and updated items will end up here
        var differentItems = updatedList.Except(originalList, new LocationFullEqualityComparer());
        // only updated items will be here
        var updatedItems = updatedList.Except(originalList, new LocationIdEqualityComparer());
        // only non-changed items will be here (item 4)
        var itemsWithoutChanges = updatedList.Intersect(originalList, new LocationFullEqualityComparer());


        Assert.That(differentItems, Has.Exactly(5).Items);
        Assert.That(updatedItems, Has.Exactly(3).Items);

        Assert.That(itemsWithoutChanges, Has.Exactly(1).Items);
    }
}

public class LocationData
{
    public LocationData(string id, string name, string address, string postCode)
    {
        Id = id;
        Name = name;
        Address = address;
        PostCode = postCode;
    }

    public string Id { get; set; }
    public string Name { get; set; }
    public string Address { get; set; }
    public string PostCode { get; set; }

    public override string ToString()
    {
        return $"{Id}, {Name}, {Address}, {PostCode}";
    }
}

// code provided by you
public class LocationFullEqualityComparer : IEqualityComparer<LocationData>
{
    public bool Equals(LocationData x, LocationData y)
    {
        bool idComparer = string.Equals(x.Id, y.Id,
            StringComparison.OrdinalIgnoreCase);
        bool nameComparer = string.Equals(x.Name, y.Name,
            StringComparison.OrdinalIgnoreCase);
        bool addressComparer = string.Equals(x.Address, y.Address,
            StringComparison.OrdinalIgnoreCase);
        bool postcodeComparer = string.Equals(x.PostCode, y.PostCode,
            StringComparison.OrdinalIgnoreCase);

        return idComparer && nameComparer && addressComparer && postcodeComparer;
    }

    public int GetHashCode(LocationData obj)
    {
        return obj.Id.GetHashCode();
    }
}
0 голосов
/ 04 августа 2020

Вы могли бы сказать, что две вещи одинаковы, если они структурно одинаковы, или вы могли бы сказать, что они одинаковы, если их идентификаторы одинаковы.

Ваш компаратор в основном проверяет, равны ли они структурно. То есть вы сравниваете все поля, чтобы выяснить, одинаковы ли все свойства.

Это означает, что вы обрабатываете LocationData как объект-значение.

Однако у него есть идентификатор, который указывает, что это действительно сущность.

К ним следует относиться по-другому, и вы можете прочитать больше об этих концепциях здесь и здесь .

С предположение, что «Id» на самом деле существует для однозначной идентификации местоположения, что в большинстве случаев будет целью «Id», проблема поиска обновлений становится тривиальной.

Один из способов может заключаться в класс расширения, такой как этот:

public static class LocationDataExt
{
    public static bool IsUpdated(this LocationData previous, LocationData current)
    {
        if (!string.Equals(previous.Id, current.Id, StringComparison.OrdinalIgnoreCase))
            return false;   // it is not updated, because it is not the same entity

        // else, return true if any other property has changed
        return !string.Equals(previous.Name, current.Name, StringComparison.OrdinalIgnoreCase) ||
                !string.Equals(previous.Address, current.Address, StringComparison.OrdinalIgnoreCase) ||
                !string.Equals(previous.PostCode, current.PostCode, StringComparison.OrdinalIgnoreCase);

    }
}

Где вы бы использовали его, чтобы найти такие обновления, как это:

var updated = currentRun
            .Where(current => previousRun.Any(previous => previous.IsUpdated(current)))
            .ToList();
0 голосов
/ 04 августа 2020

Вы можете просто написать метод, который выполняет сравнение свойств, но который возвращает true, если указанное c количество свойств совпадает (вы сказали 1 или 2, поэтому я предполагаю, что это переменная?):

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;
}

Затем вы можете просто использовать этот метод в своем операторе Linq:

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

Обратите внимание, что весьма вероятно, что несколько местоположений будут иметь один и тот же почтовый индекс, поэтому, вероятно, это должно быть не , но поскольку он не был указан, я оставил его.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...