4

このような拡張メソッドを作成しようとしました...

public static IEnumerable<T> Distinct<T>(this IEnumerable<T> value, IEnumerable<T> compareTo, Func<T, object> compareFieldPredicate)
{
    return value.Where(o => !compareTo.Exists(p => compareFieldPredicate.Invoke(p) == compareFieldPredicate.Invoke(o)));
}

こういうことができると思います...

IEnumerable<MyCollection> distinctValues = MyCollection.Distinct(MyOtherCollection, o => o.ID); //Note that o.ID is a guid

さて、この時点で、私は自分の個別のアイテムだけが私に返されることを期待していましたが、私が見つけたのは、これは決してそうではなかったということです。

さらに調査すると、次のコードを使用してこのメ​​ソッドを分解します。

Guid guid1 = Guid.NewGuid();
Guid guid2 = new Guid(guid1.ToString());

Func<MyObject, object> myFunction = o => o.ID;
Func<MyObject, object> myFunction1 = o => o.ID;

bool result = myFunction(MyObject) == myFunction1(MyObject);
//result = false

実際、GUIDが同じであっても、比較では常にfalseが返されることがわかりました。

これの原因は何ですか?

4

5 に答える 5

7

問題は、ガイドを比較する前に、ガイドをオブジェクトにボックス化することです。このコードを考えてみましょう:

Guid g1 = Guid.NewGuid();
var g2 = g1;

Console.WriteLine(g1 == g2);

object o1 = g1;
object o2 = g2;

Console.WriteLine(o1 == o2);

それは実際に出力します:

true
false

「o1」と「o2」は同じGUIDに等しいものの、同じオブジェクトではないためです。

「個別の」拡張メソッドを特定のタイプ(Guidなど)に関連付けないようにする場合は、次のようにします。

public static IEnumerable<TItem> Distinct<TItem, TProp>(this IEnumerable<TItem> value, IEnumerable<TItem> compareTo, Func<TItem, TProp> compareFieldPredicate)
    where TProp : IEquatable<TProp>
{
    return value.Where(o => !compareTo.Any(p => compareFieldPredicate(p).Equals(compareFieldPredicate(o))));
} 
于 2012-07-05T05:50:29.813 に答える
2
bool result = (guid1==guid2); //result -> true

myfunctionおよびmyfunction1で、戻りタイプObjectをGUIDに変更してみてください。

Func<MyObject, Guid> myFunction = o => o.ID;
Func<MyObject, Guid> myFunction1 = o => o.ID;

それ以外の場合、戻り値(true)はオブジェクトにボックス化され、参照の同等性がチェックされます。これはfalseです。

于 2012-07-05T05:46:58.090 に答える
1

使用するように変更

Func<MyObject, Guid> myFunction = o => o.ID;
Func<MyObject, Guid> myFunction1 = o => o.ID;

これは、関数が次のように定義されているためです。

Func<MyObject, object>

GUIDが返されmyFunctionmyFunction12つの異なるオブジェクトにボックス化されます。.NETのボクシングおよびアンボクシング機能については、こちらをご覧ください

したがって、比較が行われると、2つの異なるオブジェクトが比較されます。

inオブジェクトのデフォルトの実装はEquals、参照の同等性チェックを実行しています。ボックス化された値をチェックしていません。object.Equalsの実装方法の詳細については、ここを参照してください。

于 2012-07-05T05:47:59.887 に答える
1

他の人が言っているように、あなたのcompareFieldPredicateリターンobjectとその演算子==object.ReferenceEqualsではなく、を使用object.Equalsするので、コードは常に同等ではなくオブジェクトのアイデンティティをチェックします。

object.Equalsこれに対する1つの解決策は、演算子の代わりにメソッドを使用すること==です。

public static IEnumerable<T> Distinct<T>(
    this IEnumerable<T> value, 
    IEnumerable<T> compareTo, 
    Func<T, object> compareFieldPredicate
)
{
    return value.Where(o => !compareTo.Exists(
        p => object.Equals(compareFieldPredicate(p), compareFieldPredicate(o))
    ));
}

より良い解決策は、実際のキータイプにデフォルトの比較子を使用し、タイプIEquatableがそれ自体のインターフェイスを実装している場合はボクシングを排除します。

public static IEnumerable<T> Distinct<T, TKey>(
    this IEnumerable<T> value, 
    IEnumerable<T> compareTo, 
    Func<T, TKey> compareFieldPredicate
)
{
    return value.Where(o => !compareTo.Exists(
        p => EqualityComparer<TKey>.Default.Equals(compareFieldPredicate(p), compareFieldPredicate(o))
    ));
}

ただし、メソッドのほとんどの機能は、LINQメソッドDistinctによってすでに実装されています。
Enumerable.Except

Enumerable.Except次の実装を提供することにより、実装を書き直すことができますIEqualityComparer

private class KeyEqualityComparer<T, TKey> : IEqualityComparer<T>
{
    private readonly Func<T, TKey> _keySelector;

    public KeyEqualityComparer(Func<T, TKey> keySelector)
    { _keySelector = keySelector; }

    public int GetHashCode(T item)
    { return _keySelector(item).GetHashCode(); }

    public bool Equals(T x, T y)
    { return EqualityComparer<TKey>.Default.Equals(_keySelector(x), _keySelector(y)); }
}

public static IEnumerable<T> ExceptBy<T, TKey>(
    this IEnumerable<T> first, 
    IEnumerable<T> second, 
    Func<T, TKey> keySelector
)
{
    return first.Except(second, new KeyEqualityComparer<T, TKey>(keySelector));
}
于 2012-07-05T07:12:21.897 に答える
0

ラムダを変更してGUIDを返すと、次のように機能します。

Func<MyObject, Guid> myFunction = o => o.ID;
Func<MyObject, Guid> myFunction1 = o => o.ID;
于 2012-07-05T05:49:36.203 に答える