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)
它基本上与上面略有不同,但对我来说似乎更干净。
我认为提供从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)
如果你使用这个:
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”的类似内部类执行相同的操作(我通过反编译查看)。
Data你最好的选择是提供从到 的投影,ViewModel以便你可以说
var newData = destination.Except(data.Select(x => f(x)));
Run Code Online (Sandbox Code Playgroud)
哪里f映射Data到ViewModel. 你也需要一个IEqualityComparer<Data>。