Search code examples
c#observablelist

Remove from ObservableCollection items which name occurs on other list


I've got ObservableCollection a and List b

Now I want to remove from collection a elements that have their equivalent in list b.

My code at this moment:

public static void CrossRemove<TFirst, TSecond>(this ObservableCollection<TFirst> collection, IEnumerable<TSecond> secondCollection, Func<TFirst, TSecond, bool> predicate)
{
    collection.Where(first => secondCollection.Any(second => predicate(first, second)))
        .ToList().ForEach(item => collection.Remove(item));
}

usage:

ObservableCollection<string> first = new ObservableCollection<string> { "1", "2", "3", "4", "5", "6", "k" };

IEnumerable<int> second = new List<int> { 2, 3, 5 };

first.CrossRemove(second, (x, y) => x == y.ToString());

this code removes "2", "3" and "5" from collection leaving "1", "4", "6" and "k".

In my real code a and b contains elements that inherits from the same interface and I'm comparing property that is in that interface but I'm not able to take any adventage of it.

I can't make a new list because it's bound to wpf view and there will be visible glitches if I do that instead of removing items.

Is there any better / faster way of doing this?


Solution

  • You can make your second collection a HashSet<T> for faster lookups. I also changed your ForEach to a foreach. This is easier to demonstrate with properties, as in your original.

    void Main()
    {
        ObservableCollection<MyClass> first = new ObservableCollection<MyClass> { "1", "2", "3", "4", "5", "6", "k" };
    
        ISet<IMyInterface> second = new HashSet<IMyInterface>(new MyClass2[] { 2, 3, 5 }, new MyEqualityComparer());
    
        first.CrossRemove(second);
    
        Console.WriteLine(string.Join(", ", first.Select(x => x.MyProperty)));
        // 1, 4, 6, k
    }
    public interface IMyInterface
    {
        string MyProperty { get; set; }
    }
    public class MyEqualityComparer : IEqualityComparer<IMyInterface>
    {
        public bool Equals(IMyInterface a, IMyInterface b)
        {
            return a.MyProperty == b.MyProperty;
        }
        public int GetHashCode(IMyInterface obj)
        {
            return obj.MyProperty.GetHashCode();
        }
    }
    public static class Extensions
    {
        public static void CrossRemove<TFirst, TSecond>(this ObservableCollection<TFirst> collection, ISet<TSecond> set) where TFirst : TSecond
        {
            foreach (var item in collection.Where(item => set.Contains(item)).ToList())
                collection.Remove(item);
        }
    }
    public class MyClass : IMyInterface
    {
        public string MyProperty { get; set; }
        public static implicit operator MyClass(string s)
        {
            return new MyClass { MyProperty = s };
        }
    }
    public class MyClass2 : IMyInterface
    {
        public string MyProperty { get; set; }
        public static implicit operator MyClass2(int i)
        {
            return new MyClass2 { MyProperty = i.ToString() };
        }
    }
    

    Even if the object don't share a common interface, you should be able to write an IEqualityComparer<object> that works with both correctly, e.g. if your lambda predicate would've been:

    (TypeA a, TypeB b) => a.PropA == b.PropB
    

    Then your class would be:

    public class MyOtherEqualityComparer : IEqualityComparer<object>
    {
        private object GetProperty(object obj)
        {
            if (obj is TypeA)
                return ((TypeA)obj).PropA;
            else if (obj is TypeB)
                return ((TypeB)obj).PropB;
            else
                throw new Exception();
        }
        public bool Equals(object a, object b)
        {
            return GetProperty(a).Equals(GetProperty(b));
        }
        public int GetHashCode(object obj)
        {
            return GetProperty(obj).GetHashCode();
        }
    }