2013-09-25 33 views
1

我怎么itemsToRemove只包含“一个吧”,并itemsToAdd只包含“栏中五”?快速的方式来获得两个List <>的区别对象

我试图用“除”,但很明显,我错误地使用它。

var oldList = new List<Foo>(); 
oldList.Add(new Foo(){ Bar = "bar one"}); 
oldList.Add(new Foo(){ Bar = "bar two"}); 
oldList.Add(new Foo(){ Bar = "bar three"}); 
oldList.Add(new Foo(){ Bar = "bar four"}); 



var newList = new List<Foo>(); 
newList.Add(new Foo(){ Bar = "bar two"}); 
newList.Add(new Foo(){ Bar = "bar three"}); 
newList.Add(new Foo(){ Bar = "bar four"}); 
newList.Add(new Foo(){ Bar = "bar five"}); 


var itemsToRemove = oldList.Except(newList); // should only contain "bar one" 
var itemsToAdd = newList.Except(oldList); // should only contain "bar one" 


foreach(var item in itemsToRemove){ 
    Console.WriteLine(item.Bar + " removed"); 
    // currently says 
    // bar one removed 
    // bar two removed 
    // bar three removed 
    // bar four removed 
} 


foreach(var item in itemsToAdd){ 
    Console.WriteLine(item.Bar + " added"); 
    // currently says 
    // bar two added 
    // bar three added 
    // bar four added 
    // bar five added 
} 
+1

我认为你必须在你的Foo类中正确定义Equals,或者比较属性而不是类 –

回答

7

Except将使用默认Equals和有关对象的GetHashCode方法来定义“平等”的对象,除非您提供自定义比较(你没有)。在这种情况下,将比较对象的引用,而不是他们Bar值。

一种选择是创建一个IEqualityComparer<Foo>,它比较Bar属性,而不是对对象本身的引用。

public class FooComparer : IEqualityComparer<Foo> 
{ 
    public bool Equals(Foo x, Foo y) 
    { 
     if (x == null^y == null) 
      return false; 
     if (x == null && y == null) 
      return true; 
     return x.Bar == y.Bar; 
    } 

    public int GetHashCode(Foo obj) 
    { 
     if (obj == null) 
      return 0; 
     return obj.Bar.GetHashCode(); 
    } 
} 

另一个选择是创建一个Except方法接受一个选择器的值上进行比较。我们可以创建这样的方法,然后使用:

public static IEnumerable<TSource> ExceptBy<TSource, TKey>(
    this IEnumerable<TSource> first, 
    IEnumerable<TSource> second, 
    Func<TSource, TKey> keySelector, 
    IEqualityComparer<TKey> comparer = null) 
{ 
    comparer = comparer ?? EqualityComparer<TKey>.Default; 
    var set = new HashSet<TKey>(second.Select(keySelector), comparer); 
    return first.Where(item => set.Add(keySelector(item))); 
} 

这让我们写:

var itemsToRemove = oldList.ExceptBy(newList, foo => foo.Bar); 
var itemsToAdd = newList.ExceptBy(oldList, foo => foo.Bar); 
+0

这太好了。谢谢Servy! –

0

这是因为你比较Foo类型,而不是string类型的属性栏的对象。尝试:

var itemsToRemove = oldList.Select(i => i.Bar).Except(newList.Select(i => i.Bar)); 
var itemsToAdd = newList.Select(i => i.Bar).Except(oldList.Select(i => i.Bar)); 
+3

这会在最后使用'Bar'对象而不是'Foo'对象。 – Servy

+1

优秀点。我会提高你的答案。 – Haney

2

你的逻辑是合理的,但对于比较两类Except默认行为是通过引用去。由于您有效地创建了两个带8个不同objets(不管其内容)的列表,因此不会有两个相同的对象。

可以,但是,使用Except overload that takes an IEqualityComparer。例如:

public class FooEqualityComparer : IEqualityComparer<Foo> 
{ 
    public bool Equals(Foo left, Foo right) 
    { 
     if(left == null && right == null) return true; 

     return left != null && right != null && left.Bar == right.Bar; 
    } 

    public int GetHashCode(Foo item) 
    { 
     return item != null ? item.Bar.GetHashcode() : 0; 
    } 
} 

// In your code 

var comparer = new FooEqualityComparer(); 
var itemsToRemove = oldList.Except(newList, comparer); 
var itemsToAdd = newList.Except(oldList, comparer); 
0

在您的数据对象上实现IComparable;我认为你被参考比较咬了。如果将Foo更改为字符串,则代码有效。

 var oldList = new List<string>(); 
     oldList.Add("bar one"); 
     oldList.Add("bar two"); 
     oldList.Add("bar three"); 
     oldList.Add("bar four"); 

     var newList = new List<string>(); 
     newList.Add("bar two"); 
     newList.Add("bar three"); 
     newList.Add("bar four"); 
     newList.Add("bar five"); 

     var itemsToRemove = oldList.Except(newList); // should only contain "bar one" 
     var itemsToAdd = newList.Except(oldList); // should only contain "bar one" 

     foreach (var item in itemsToRemove) 
     { 
      Console.WriteLine(item + " removed"); 
     } 


     foreach (var item in itemsToAdd) 
     { 
      Console.WriteLine(item + " added"); 
     } 
1

这是大多Servy的回答一个即兴给这一个更通用的方法:

public class PropertyEqualityComparer<TItem, TKey> : EqualityComparer<Tuple<TItem, TKey>> 
{ 
    readonly Func<TItem, TKey> _getter; 
    public PropertyEqualityComparer(Func<TItem, TKey> getter) 
    { 
     _getter = getter; 
    } 

    public Tuple<TItem, TKey> Wrap(TItem item) { 
     return Tuple.Create(item, _getter(item)); 
    } 

    public TItem Unwrap(Tuple<TItem, TKey> tuple) { 
     return tuple.Item1; 
    } 

    public override bool Equals(Tuple<TItem, TKey> x, Tuple<TItem, TKey> y) 
    { 
     if (x.Item2 == null && y.Item2 == null) return true; 
     if (x.Item2 == null || y.Item2 == null) return false; 
     return x.Item2.Equals(y.Item2); 
    } 

    public override int GetHashCode(Tuple<TItem, TKey> obj) 
    { 

     if (obj.Item2 == null) return 0; 
     return obj.Item2.GetHashCode(); 
    } 
} 

public static class ComparerLinqExtensions { 
    public static IEnumerable<TSource> Except<TSource, TKey>(this IEnumerable<TSource> first, IEnumerable<TSource> second, Func<TSource, TKey> keyGetter) 
    { 
     var comparer = new PropertyEqualityComparer<TSource, TKey>(keyGetter); 
     var firstTuples = first.Select(comparer.Wrap); 
     var secondTuples = second.Select(comparer.Wrap); 
     return firstTuples.Except(secondTuples, comparer) 
          .Select(comparer.Unwrap); 
    } 
} 
// ... 
var itemsToRemove = oldList.Except(newList, foo => foo.Bar); 
var itemsToAdd = newList.Except(oldList, foo => foo.Bar); 

这应该工作的优良任何类不寻常的平等的语义,它是不正确调用object.Equals()覆盖代替IEquatable<T>.Equals()。值得注意的,这将正常工作的匿名类型。

+0

在这种情况下,这不会成为问题,但对于此解决方案要小心,因为它会为每个对象多次调用属性getter。如果计算计算成本昂贵,或者存在副作用,则可能会导致问题。你的类也不会处理投影值中的空值,并会将任何投影值类型框起来。我提供的第二种解决方案提供了相同的一般功能,没有任何这些缺点。 – Servy

+0

值得注意的是,匿名类型会覆盖它们的“Equals”方法来比较所有基础值,而不是使用身份语义。 – Servy

+0

我承认我的是一个快速而肮脏的解决方案。它实际上主要是用来从对象中获取一个或多个属性并将其用作比较键。我同意的无效安全是有问题的。你提到的匿名类型的属性在这里是可取的 - 这个想法是能够表达,比如'people.Distinct(person => new {person.FirstName,person.LastName});'succintly。 – millimoose

相关问题