LINQ: Использовать .Except () для коллекций разных типов, делая их конвертируемыми / сопоставимыми? - PullRequest
6 голосов
/ 03 апреля 2012

Учитывая два списка разных типов, возможно ли сделать эти типы конвертируемыми между собой или сопоставимыми друг с другом (например, с помощью TypeConverter или аналогичных), чтобы запрос LINQ мог их сравнить?Я видел другие подобные вопросы о SO, но ничего, что указывало на то, что типы могли быть преобразованы друг в друга для решения проблемы.

Типы коллекций:

public class Data
{
    public int ID { get; set; }
}

public class ViewModel
{
    private Data _data;

    public ViewModel(Data data)
    {
        _data = data;
    }
}

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

    public void DoMerge(ObservableCollection<ViewModel> destination, IEnumerable<Data> data)
    {
        // 1. Find items in data that don't already exist in destination
        var newData = destination.Except(data);

        // ...
    }

Казалось бы логичным, что, поскольку я знаю, как сравнивать экземпляр ViewModel с экземпляром Data, я должен быть в состоянии предоставить некоторую логику сравнения, которую LINQ затем использовал бы для запросов, таких как .Except ().Возможно ли это?

Ответы [ 3 ]

6 голосов
/ 03 апреля 2012

Я предполагаю, что предоставление прогноза от Data до ViewModel проблематично, поэтому я предлагаю другое решение в дополнение к Джейсону.

За исключением того, что использует хэш-набор (если я правильно помню),так что вы можете получить аналогичную производительность, создав собственный хэш-сет.Я также предполагаю, что вы идентифицируете Data объекты как равные, когда их IDs равны.

var oldIDs = new HashSet<int>(data.Select(d => d.ID));
var newData = destination.Where(vm => !oldIDs.Contains(vm.Data.ID));

Возможно, у вас есть другое использование для коллекции "oldData" в другом месте метода, вВ этом случае вы хотели бы сделать это вместо этого.Либо внедрите IEquatable<Data> в свой класс данных, либо создайте пользовательский IEqualityComparer<Data> для хеш-набора:

var oldData = new HashSet<Data>(data);
//or: var oldData = new HashSet<Data>(data, new DataEqualityComparer());
var newData = destination.Where(vm => !oldData.Contains(vm.Data));
4 голосов
/ 26 июня 2014

Если вы используете это:

var newData = destination.Except(data.Select(x => f(x)));

Вы должны проецировать 'данные' на тот же тип, который содержится в 'destination', но с помощью приведенного ниже кода вы можете избавиться от этого ограничения:

//Here is how you can compare two different sets.
class A { public string Bar { get; set; } }
class B { public string Foo { get; set; } }

IEnumerable<A> setOfA = new A[] { /*...*/ };
IEnumerable<B> setOfB = new B[] { /*...*/ };
var subSetOfA1 = setOfA.Except(setOfB, a => a.Bar, b => b.Foo);

//alternatively you can do it with a custom EqualityComparer, if your not case sensitive for instance.
var subSetOfA2 = setOfA.Except(setOfB, a => a.Bar, b => b.Foo, StringComparer.OrdinalIgnoreCase);

//Here is the extension class definition allowing you to use the code above
public static class IEnumerableExtension
{
    public static IEnumerable<TFirst> Except<TFirst, TSecond, TCompared>(
        this IEnumerable<TFirst> first,
        IEnumerable<TSecond> second,
        Func<TFirst, TCompared> firstSelect,
        Func<TSecond, TCompared> secondSelect)
    {
        return Except(first, second, firstSelect, secondSelect, EqualityComparer<TCompared>.Default);
    }

    public static IEnumerable<TFirst> Except<TFirst, TSecond, TCompared>(
        this IEnumerable<TFirst> first,
        IEnumerable<TSecond> second,
        Func<TFirst, TCompared> firstSelect,
        Func<TSecond, TCompared> secondSelect,
        IEqualityComparer<TCompared> comparer)
    {
        if (first == null)
            throw new ArgumentNullException("first");
        if (second == null)
            throw new ArgumentNullException("second");
        return ExceptIterator<TFirst, TSecond, TCompared>(first, second, firstSelect, secondSelect, comparer);
    }

    private static IEnumerable<TFirst> ExceptIterator<TFirst, TSecond, TCompared>(
        IEnumerable<TFirst> first,
        IEnumerable<TSecond> second,
        Func<TFirst, TCompared> firstSelect,
        Func<TSecond, TCompared> secondSelect,
        IEqualityComparer<TCompared> comparer)
    {
        HashSet<TCompared> set = new HashSet<TCompared>(second.Select(secondSelect), comparer);
        foreach (TFirst tSource1 in first)
            if (set.Add(firstSelect(tSource1)))
                yield return tSource1;
    }
}

Некоторые могут утверждать, что память неэффективна из-за использования HashSet.Но на самом деле метод Enumerable.Except в фреймворке делает то же самое с аналогичным внутренним классом под названием Set (я посмотрел, декомпилируя).

4 голосов
/ 03 апреля 2012

Лучше всего предоставить прогноз от Data до ViewModel, чтобы вы могли сказать

var newData = destination.Except(data.Select(x => f(x)));

, где f отображает Data на ViewModel.Вам тоже понадобится IEqualityComparer<Data>.

...