LINQ:通过使它们可转换/可比较,对不同类型的集合使用.Except()吗?

Jam*_*add 6 c# linq comparison equality comparable

给定两个不同类型的列表,是否可以使这些类型在彼此之间可转换或相互比较(例如使用TypeConverter或类似),以便LINQ查询可以比较它们?我已经在SO上看过其他类似的问题,但没有任何指出使类型可以在彼此之间转换以解决问题.

收集类型:

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

public class ViewModel
{
    private Data _data;

    public ViewModel(Data data)
    {
        _data = data;
    }
}
Run Code Online (Sandbox Code Playgroud)

所需用法:

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

        // ...
    }
Run Code Online (Sandbox Code Playgroud)

这似乎是合乎逻辑的,因为我知道如何将ViewModel的实例与Data的实例进行比较,我应该能够提供一些比较逻辑,然后LINQ将用于查询,例如.Except().这可能吗?

Geo*_*ott 10

我知道这已经晚了,但是使用 Func 有一种更简单的语法,可以消除对比较器的需要。

public static class LinqExtensions
{
   public static IEnumerable<TSource> Except<TSource, VSource>(this IEnumerable<TSource> first, IEnumerable<VSource> second, Func<TSource, VSource, bool> comparer)
   {
       return first.Where(x => second.Count(y => comparer(x, y)) == 0);
   }

   public static IEnumerable<TSource> Contains<TSource, VSource>(this IEnumerable<TSource> first, IEnumerable<VSource> second, Func<TSource, VSource, bool> comparer)
   {
       return first.Where(x => second.FirstOrDefault(y => comparer(x, y)) != null);
   }

   public static IEnumerable<TSource> Intersect<TSource, VSource>(this IEnumerable<TSource> first, IEnumerable<VSource> second, Func<TSource, VSource, bool> comparer)
   {
       return first.Where(x => second.Count(y => comparer(x, y)) == 1);
   }
}
Run Code Online (Sandbox Code Playgroud)

所以对于 Foo 和 Bar 类的列表

public class Bar
{
   public int Id { get; set; }
   public string OtherBar { get; set; }
}

public class Foo
{
   public int Id { get; set; }
   public string OtherFoo { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

可以运行 Linq 语句,例如

var fooExceptBar = fooList.Except(barList, (f, b) => f.Id == b.Id);
var barExceptFoo = barList.Except(fooList, (b, f) => b.OtherBar == f.OtherFoo);
Run Code Online (Sandbox Code Playgroud)

它基本上与上面略有不同,但对我来说似乎更干净。


pho*_*oog 6

我认为提供从Data到的投影ViewModel是有问题的,因此除了Jason的解决方案之外,我还提供了另一种解决方案。

除了使用哈希集(如果我没记错的话),因此您可以通过创建自己的哈希集来获得类似的性能。我还假设您在Data对象相等时将它们标识为IDs相等。

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

您可能在该方法的其他地方还有一个“ 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));
Run Code Online (Sandbox Code Playgroud)


Pat*_*ien 5

如果你使用这个:

var newData = destination.Except(data.Select(x => f(x)));
Run Code Online (Sandbox Code Playgroud)

您必须将“数据”投影到“目标”中包含的相同类型,但使用下面的代码可以摆脱此限制:

//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;
    }
}
Run Code Online (Sandbox Code Playgroud)

有些人可能会认为,由于使用了 HashSet,内存效率低下。但实际上框架的 Enumerable.Except 方法正在对一个名为“Set”的类似内部类执行相同的操作(我通过反编译查看)。


jas*_*son 4

Data你最好的选择是提供从到 的投影,ViewModel以便你可以说

var newData = destination.Except(data.Select(x => f(x)));
Run Code Online (Sandbox Code Playgroud)

哪里f映射DataViewModel. 你也需要一个IEqualityComparer<Data>