Удалить из ObservableCollection элементы, имя которых встречается в другом списке

У меня ObservableCollection a и List b

Теперь я хочу удалить из коллекции a элементы, у которых есть эквивалент в списке b.

Мой код на данный момент:

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

использование:

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());

этот код удаляет «2», «3» и «5» из коллекции, оставляя «1», «4», «6» и «k».

В моем реальном коде a и b содержат элементы, унаследованные от того же interface, и я сравниваю свойства, которые есть в этом интерфейсе, но я не могу воспользоваться этим.

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

Есть ли лучший / более быстрый способ сделать это?


person Adassko    schedule 28.10.2013    source источник
comment
В чем именно заключается ваш вопрос?   -  person Sriram Sakthivel    schedule 28.10.2013
comment
Я не могу придумать лучшего способа. Вам придется придерживаться этого подхода. Может кто-нибудь придет с лучшим решением :)   -  person Sriram Sakthivel    schedule 28.10.2013


Ответы (2)


Вы можете сделать свою вторую коллекцию HashSet<T> для более быстрого поиска. Я также изменил ваш ForEach на foreach < / а>. Это проще продемонстрировать с помощью свойств, как в вашем оригинале.

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

Даже если у объекта нет общего интерфейса, вы должны иметь возможность написать IEqualityComparer<object>, который правильно работает с обоими, например если бы ваш лямбда-предикат был бы таким:

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

Тогда ваш класс будет:

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();
    }
}
person Tim S.    schedule 28.10.2013
comment
Спасибо, что нашли время, я этим воспользуюсь :) И последнее: вы можете придумать имя лучше, чем CrossRemove, потому что оно мало говорит и английский - не мой родной язык;) - person Adassko; 29.10.2013
comment
Вы можете использовать несколько имен: Difference, Complement или Subtract (из математической идеи множества), или RemoveAll, как метод List<T> (за исключением того, что он принимает предикат, а не коллекцию), или RemoveWhere, как метод HashSet<T> (который также принимает предикат). Думаю, я бы выбрал Subtract или RemoveAll. - person Tim S.; 29.10.2013

Я думаю, что самый простой способ сделать это - использовать эквивалент List<T> RemoveAll функция как более общая. Вместо

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

Я бы написал

first.RemoveAll(item1 => second.Any(item2 => item1 == item2.ToString()));

К сожалению, у ObservableCollection<T> нет этого метода, поэтому нам нужно написать его:

public static class Extensions
{
    public static void RemoveAll<T>(this ICollection<T> collection, Func<T, bool> pred)
    {
        var toBeRemoved = collection.Where(pred).ToArray();
        foreach (var item in toBeRemoved)
            collection.Remove(item);
    }
}

РЕДАКТИРОВАТЬ:

Вышеупомянутый метод расширения очень неэффективен, другие, такие как этот, алгоритмически намного быстрее. В данном случае, однако, я не думаю, что это актуально, поскольку мы говорим о ObservableCollection<T>, который предположительно связан с представлением. Учитывая это, мы должны вносить очень небольшое количество изменений, иначе затраты на макет и повторный рендеринг будут очень высокими. Если вы вносите большое количество изменений, вам, вероятно, следует заменить коллекцию новой, чтобы макет пересчитывался только один раз.

person Matthew Finlay    schedule 08.05.2014