1

itemsToRemove「小節 1」itemsToAddのみを含め、「小節 5」のみを含めるにはどうすればよいですか?

「Except」を使おうとしているのですが、明らかに使い方が間違っています。

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
}
4

5 に答える 5

7

Exceptカスタム比較子を提供しない限り、問題のオブジェクトのデフォルトEqualsとメソッドを使用して、オブジェクトの「同等性」を定義します(提供していません)。GetHashCodeこの場合、それはオブジェクトのBar値ではなく参照を比較します。

1 つのオプションは、オブジェクト自体への参照ではなく、プロパティ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);
于 2013-09-25T18:21:45.357 に答える
2

あなたのロジックは健全ですが、Except2 つのクラスを比較するためのデフォルトの動作は参照によるものです。(内容に関係なく) 8 つの異なるオブジェクトを持つ 2 つのリストを効果的に作成しているため、2 つの等しいオブジェクトは存在しません。

ただし、ExceptIEqualityComparer を受け取るオーバーロードを使用できます。例えば:

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); 
于 2013-09-25T18:23:34.110 に答える
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()ん。特に、これは匿名型で正常に機能します。

于 2013-09-25T18:36:40.163 に答える
0

Fooこれは、 typeのプロパティ Bar ではなく、 type のオブジェクトを比較しているためです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));
于 2013-09-25T18:21:18.663 に答える
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");
        }
于 2013-09-25T18:25:50.090 に答える