Компаратор DefaultEquality в LINQ - PullRequest
0 голосов
/ 12 мая 2011

У меня есть следующий код

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace ConsoleApplication2
{
    class Program
    {
        static void Main(string[] args)
        {
            List<I> bars = new List<I>();
            bars.Add(new A() { Id = 1 });
            bars.Add(new B() { Id = 1 });
            bars.Add(new A() { Id = 1 });

            var distictBars = bars.Distinct();

            foreach (var item in distictBars)
            {
                Debug.WriteLine(item.Name);
            }
        }
    }

    interface I
    {
        string Name { get; }
    }

    class Base
    {
        public int Id { get; set; }
    }

    class A : Base, I, IEquatable<A>
    {
        public string Name
        {
            get { return this.Id + "-A"; }
        }

        public bool Equals(A other)
        {
            return this.Id == other.Id;
        }

        public override bool Equals(object obj)
        {
            if (obj is A)
                return this.Equals(obj as A);
            else
                return object.ReferenceEquals(this, obj);
        }
    }

    class B : Base, I, IEquatable<B>
    {
        public string Name
        {
            get { return this.Id + "-B"; }
        }

        public bool Equals(B other)
        {
            return this.Id == other.Id;
        }

        public override bool Equals(object obj)
        {
            if (obj is A)
                return this.Equals(obj as A);
            else
                return object.ReferenceEquals(this, obj);
        }
    }
}

Вывод следующий

1-A 
1-B 
1-A 

Мне нужно, однако, следующее

1-A 
1-B

, то есть устраните дубликаты типа A или B. Похоже, что метод Distinct LINQ не учитывал ни переопределение Equals, ни реализацию интерфейса IEquitable.

Как мне поступить по порядку, чем этот метод делает указанное сравнение?

Ответы [ 2 ]

2 голосов
/ 12 мая 2011

Метод Distinct, скорее всего, будет использовать что-то похожее на HashSet<T> внутри, что зависит от хеш-кода (очевидно).Вам также нужно переопределить GetHashCode.


Мне кажется, у вас вообще есть ненужное дублирование.Оба ваших класса A и B используют в основном одну и ту же логику для определения равенства;что вы хотите, чтобы два Base объекта были равны, если их идентификаторы равны и они одного типа?(Вот как это выглядит для меня; ваша реализация, кажется, содержит пару ошибок в этом случае, но это все еще мое лучшее предположение.)

Если это так, просто сделайте Base внедрить IEquatable<Base> иВаши реализации A и B становятся намного проще:

class Base : IEquatable<Base>
{
    public Base(int id)
    {
        Id = id;
    }

    public int Id { get; private set; }

    public bool Equals(Base other)
    {
        if (other == null)
        {
            return false;
        }

        // Ensure As only compare equal with other As
        // and the same for Bs (and potentially other subtypes).
        return other.GetType().Equals(GetType()) && other.Id == Id;
    }

    public override bool Equals(object other)
    {
        if (other is Base)
        {
            return Equals((Base)other);
        }

        return false;
    }

    public override int GetHashCode()
    {
        return Id;
    }
}

class A : Base, I
{
    public A(int id) : base(id)
    { }

    public string Name
    {
        get { return this.Id + "-A"; }
    }
}

class B : Base, I
{
    public B(int id) : base(id)
    { }

    public string Name
    {
        get { return this.Id + "-B"; }
    }
}

Обратите внимание, что я также изменил установщик свойства Id на private;в общем, очень рискованная игра - сделать поле или свойство доступным для записи, когда оно используется в реализации GetHashCode типа (поскольку, если хеш-код объекта когда-либо изменяется, он перестает работать как ключ в хеш-таблице).


Этот комментарий становится ненужным, если вы примените мою рекомендацию выше.

Также, предупреждение! Мне кажется, что вы рискуетебесконечной рекурсии в вашем B.Equals методе:

    public override bool Equals(object obj)
    {
        if (obj is A)
            // There is no method with the signature Equals(A);
            // so this will just call your Equals(object) method --
            // i.e., THIS method -- over and over again.
            return this.Equals(obj as A);
        else
            return object.ReferenceEquals(this, obj);
    }
1 голос
/ 12 мая 2011

Вам необходимо переопределить .GetHashCode() в ваших объектах. Я предлагаю что-то вроде этого:

public override int GetHashCode() {
    return this.Id;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...