Linq compare two ObservableCollection<T> with Except

1.4k views Asked by At

I have read about the IEqualityComparer interface. Here is my code (which says more then a thousand words)

static void Main(string[] args)
{
    var Send = new ObservableCollection<ProdRow>() {
        new ProdRow() { Code = "8718607000065", Quantity = 1 },
        new ProdRow() { Code = "8718607000911", Quantity = 10 }
    };
    var WouldSend = new ObservableCollection<ProdRow>() {
        new ProdRow() { Code = "8718607000065", Quantity = 1 },
        new ProdRow() { Code = "8718607000072", Quantity = 1 },
        new ProdRow() { Code = "8718607000256", Quantity = 1 },
        new ProdRow() { Code = "8718607000485", Quantity = 1 },
        new ProdRow() { Code = "8718607000737", Quantity = 1 },
        new ProdRow() { Code = "8718607000911", Quantity = 20 }
    };

    //var sendToMuch = Send.Except(WouldSend).ToList();
    //var sendToLittle = WouldSend.Except(Send).ToList();

    //if (sendToMuch.Any() || sendToLittle.Any())
    //    var notGood = true;
    //else
    //    var okay = true;

    var sendToMuch = Send.ToList();
    var sendToLittle = WouldSend.ToList();

    foreach (var s in Send) {
        var w = WouldSend.FirstOrDefault(d => d.Code.Equals(s.Code));

        if (w != null) {
            if (w.Quantity == s.Quantity) {
                sendToMuch.Remove(s);
                sendToLittle.Remove(w);
                continue;
            }
            if (w.Quantity > s.Quantity) {
                sendToLittle.Single(l => l.Code == w.Code).Quantity = (w.Quantity - s.Quantity);
                sendToMuch.Remove(s);
            } else {
                sendToMuch.Single(l => l.Code == w.Code).Quantity = (s.Quantity - w.Quantity);
                sendToLittle.Remove(s);
            }
        } else {
            sendToMuch.Add(s);
        }
    }
}

The commented lines where what I would hoped that would work... the stuff below with what I ended up with.

As reference, here is my ProdRow class:

class ProdRow : INotifyPropertyChanged, IEqualityComparer<ProdRow>
{
    private string _code;
    private int _quantity;
    public string Code {
        get { return _code; }
        set {
            _code = value;
            OnPropertyChanged("Code");
        }
    }
    public int Quantity {
        get { return _quantity; }
        set {
            _quantity = value;
            OnPropertyChanged("Quantity");
        }
    }

    private void OnPropertyChanged(string v) {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(v));
    }

    public new bool Equals(object x, object y) {
        if (((ProdRow)x).Code.Equals(((ProdRow)y).Code) && ((ProdRow)x).Quantity == ((ProdRow)y).Quantity)
            return true;
        else
            return false;
    }
    public int GetHashCode(object obj) {
        return obj.GetHashCode();
    }
    public bool Equals(ProdRow x, ProdRow y) {
        if (x.Code.Equals(y.Code) && x.Quantity == y.Quantity)
            return true;
        else
            return false;
    }
    public int GetHashCode(ProdRow obj) {
        throw new NotImplementedException();
    }
    public event PropertyChangedEventHandler PropertyChanged;
}

I did not expected the commented part to work, because it cannot know to decrease the int of quantity etc. but I would like to know if there is a more efficient way to do this then the solution I used (below the commented lines). Perhaps flatten the collection like a string[]?

P.S. Sorry for the "PascalCase" of Send and WouldSend

1

There are 1 answers

3
Sergey Kalinichenko On BEST ANSWER

IEqualityComparer<T> is not the right interface to implement for a class whose instances you wish to compare. IEqualityComparer<T> implementations are for creating objects that do comparisons from the outside of the objects being compared, which becomes important when you need to re-define what it means for two objects to be equal without access to the code of these objects, or when you need to use different semantic for equality depending on the context.

The right interface for strongly typed equality comparison is IEquatable<T>. However, in your case all you need is overriding Object's Equals(object) and GetHashCode():

public new bool Equals(object obj) {
    if (obj == this) return true;
    var other = obj as ProdRow;
    if (other == null) return false;
    return Code.Equals(other.Code) && Quantity == other.Quantity;
}
public int GetHashCode() {
    return 31*Code.GetHashCode() + Quantity;
}

As far as computing quantities goes, you can do it with negative numbers and GroupBy:

var quantityByCode = WouldSend.Select(p => new {p.Code, p.Quantity})
    .Concat(Send.Select(p => new {p.Code, Quantity = -p.Quantity}))
    .GroupBy(p => p.Code)
    .ToDictionary(g => g.Key, g => g.Sum(p => p.Quantity));
var tooLittle = quantityByCode
    .Where(p => p.Value > 0)
    .Select(p => new ProdRow {Code = p.Key, Quantity = p.Value})
    .ToList();
var tooMuch = quantityByCode
    .Where(p => p.Value < 0)
    .Select(p => new ProdRow {Code = p.Key, Quantity = -p.Value})
    .ToList();