林克:使用 .Except() 通过使它们可转换/可比较来对不同类型的集合进行
本文关键字:可比较 同类型 集合 可转换 Except 使用 林克 | 更新日期: 2023-09-27 18:29:57
给定两个不同类型的列表,是否可以使这些类型彼此可转换或相互比较(例如,使用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 将使用这些逻辑进行查询,例如 。除了((。 这可能吗?
这已经晚了,但是使用 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);
}
}
所以有类 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; }
}
可以运行 Linq 语句,例如
var fooExceptBar = fooList.Except(barList, (f, b) => f.Id == b.Id);
var barExceptFoo = barList.Except(fooList, (b, f) => b.OtherBar == f.OtherFoo);
它基本上是上面的轻微变化,但对我来说似乎更干净。
我认为提供从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));
您可能在方法中的其他位置有"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));
如果你使用这个:
var newData = destination.Except(data.Select(x => f(x)));
您必须将"数据"投影到"目标"中包含的相同类型,但是使用以下代码可以摆脱此限制:
//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"的类似内部类做同样的事情(我通过反编译查看了一下(。
最好的办法是提供从Data
到ViewModel
的投影,以便您可以说
var newData = destination.Except(data.Select(x => f(x)));
f
映射到ViewModel
Data
的位置。您还需要IEqualityComparer<Data>
。
肮脏的伎俩:将两个列表投影到它们的共享 ID,执行Except
,然后使用带有原始列表的Join
将结果列表再次投影到原始类型,以添加您在第一个Select
中删除的其余属性。可能性能不是很好,但对于小列表来说还可以。
listOfType1.Select(x => x.Id)
.Except(listOfType2.Select(x => x.Id))
.Join(listOfType1,
onlyIds => onlyIds,
fullData => fullData.Id,
(onlyIds, fullData)
=> new Type1
{
Id = fullData.Id,
OtherPropertyOfType1 = fullData.OtherPropertyOfType1
});