Список равенства пользовательского класса - PullRequest
0 голосов
/ 28 июня 2019

У меня есть класс A, который содержит строковое свойство и перезаписывает Equals для проверки на равенство.

public class A
{
    public string Prop { get; }

    public A(string val)
    {
        Prop = val;
    }

    public override bool Equals(object obj)
    {
        return obj is A arg && (Prop == arg.Prop);
    }

    public override int GetHashCode()
    {
        return base.GetHashCode();
    }
}

У меня также есть класс B, который имеет List<A> в качестве свойства:

public class B
{
    public IReadOnlyList<A> Prop { get; }

    public B(IReadOnlyList<A> val)
    {
        Prop = val;
    }

    public override bool Equals(object obj)
    {
        // ...
    }

    public override int GetHashCode()
    {
        return base.GetHashCode();
    }
}

Я хочу быть в состоянии сравнить с экземплярами B для равенства и порядка.Как я могу написать метод Equals в B, не переписывая тот же код, который я написал в A?Есть ли способ повторно использовать A Equals?

Ответы [ 4 ]

2 голосов
/ 28 июня 2019

Обновление: Предполагается, что моя первая версия B является производной от A.

  1. A.Equals

Если A не запечатан, obj is A ... может возвращать ложное срабатывание, если сравниваются разные типы. Итак исправленная версия:

public override bool Equals(object obj)
{
    return obj is A other
        && this.Prop == other.Prop
        && this.GetType() == other.GetType(); // not needed if A is sealed
}
  1. A.GetHashCode

base.GetHashCode вернет разные хеш-коды для разных, но одинаковых экземпляров, что неверно. Вместо этого выведите хеш-код из собственных свойств. Если Prop действует как некоторый идентификатор, просто верните Prop.GetHashCode()

  1. B.Equals
    public override bool Equals(object obj)
    {
        return obj is B other
            && this.Prop.SequenceEqual(other.Prop) // will re-use A.Equals
            && this.Prop.GetType() == other.Prop.GetType() // not needed if different IReadOnlyList types are ok
            && this.GetType() == other.GetType(); // not needed if B is sealed
    }
  1. B.GetHashCode

Вы можете объединить хеш-коды A экземпляров. Здесь я использую простой XOR, но если одни и те же предметы часто могут приходить в другом порядке, вы можете придумать что-нибудь более причудливое.

return Prop.Aggregate(0, (h, i) => h ^ i.GetHashCode());
1 голос
/ 28 июня 2019

Реализация Equals для списка может быть выполнена с помощью метода SequenceEquals (из пространства имен System.Linq), который гарантирует, что каждый элемент в одном списке равен элементу с тем же индексом в другом списке.

Одной вещью, которую вы могли бы изменить, однако, является ваша реализация GetHashCode.Этот метод должен возвращать одно и то же число, если два элемента равны (хотя не гарантируется, что два элемента с одинаковым хеш-кодом равны).Использование base.GetHashCode() не отвечает этому требованию, поскольку base в этом случае равно object;согласно документация , "хеш-коды для ссылочных типов вычисляются путем вызова метода Object.GetHashCode базового класса, который вычисляет хеш-код на основе ссылки на объект" , поэтомуобъекты возвращают один и тот же HashCode, если они ссылаются на один и тот же объект.

HashCode должен основываться на тех же свойствах, которые используются для определения равенства, поэтому в этом случае мы хотим использовать Prop.GetHashCode() для классаA, и мы хотим объединить хеш-код для всех элементов в Prop для класса B.

Вот один из способов, которым классы могут быть реорганизованы:

public class A : IEquatable<A>
{
    public string Prop { get; }

    public A(string val)
    {
        Prop = val;
    }

    public bool Equals(A other)
    {
        if (other == null) return false;
        return Prop == other.Prop;
    }

    public override bool Equals(object obj)
    {
        return Equals(obj as A);
    }

    public override int GetHashCode()
    {
        return Prop.GetHashCode();
    }
}

public class B : IEquatable<B>
{
    public IReadOnlyList<A> Prop { get; }

    public B(IReadOnlyList<A> val)
    {
        Prop = val;
    }

    public bool Equals(B other)
    {
        if (other == null) return false;
        if (ReferenceEquals(this, other)) return true;
        if (Prop == null) return other.Prop == null;
        return other.Prop != null && Prop.SequenceEqual(other.Prop);
    }

    public override bool Equals(object obj)
    {
        return Equals(obj as B);
    }

    public override int GetHashCode()
    {
        return Prop?.Aggregate(17,
            (current, item) => current * 17 + item?.GetHashCode() ?? 0)
                ?? 0;
    }
}
1 голос
/ 28 июня 2019

Linq содержит полезный метод для сравнения коллекций: SequenceEqual

public override bool Equals(object obj)
{
    if (!(obj is B other))
    {
        return false;
    }

    if (this.Prop == null || other.Prop == null)
    {
        return false;
    }

    return this.Prop.SequenceEqual(other.Prop);
}

Также реализуйте IEquatable<T> при переопределении Equals.

0 голосов
/ 28 июня 2019

Как насчет этого:

public override bool Equals(object obj)
{
    if(!(obj is B))
    {
        return false;
    }

    var b = obj as B;

    if(b.Prop.Count != this.Prop.Count)
    {
        return false;
    }

    for(var i =0; i < Prop.Count; i++)
    {
        if (!Prop.ElementAt(i).Equals(b.Prop.ElementAt(i)))
        {
            return false;
        }
    }

    return true;
}
...