Reputation: 1359
I have a class A
, which holds a string property and overwrites Equals
for equality testing.
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();
}
}
I also have a class B
which has a List<A>
as property:
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();
}
}
I wanna be able to compare to instances of B for equality and order. How can I write the Equals method in B by not rewriting the same code I wrote in A? Is there a way to reuse the A Equals?
Upvotes: 0
Views: 658
Reputation: 37060
Implementing Equals
for a list can be done by using the SequenceEquals
method (from System.Linq
namespace), which ensures that each item in one list equals the item at the same index in the other list.
One thing you might consider changing, however is your implementation of GetHashCode
. This method should return the same number if two items are equal (though it's not guaranteed that two items with the same hash code are equal). Using base.GetHashCode()
does not meet this requirement, since the base
is object
in this case; according to the documentation, "hash codes for reference types are computed by calling the Object.GetHashCode method of the base class, which computes a hash code based on an object's reference", so objects only return the same HashCode if they refer to the exact same object.
The HashCode
should be based on the same properties used to determine equality, so in this case we want to use Prop.GetHashCode()
for class A
, and we want to aggregate the hashcode for all the items in Prop
for class B
.
Here's one way the classes could be refactored:
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;
}
}
Upvotes: 1
Reputation: 18023
Update: My first version assumed B
is derived from A
.
A.Equals
:If A
is not sealed, obj is A ...
can return a false positive if different types are compared. So the corrected version:
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
}
A.GetHashCode
:base.GetHashCode
will return different hash codes for different but equal instances, which is wrong. Derive the hashcode from self properties instead. If Prop
acts like some ID, then simply return Prop.GetHashCode()
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
}
B.GetHashCode
:You can aggregate the hash codes of A
instances. Here I use a simple XOR but if the same items can often come in a different order you can come up with something more fancy.
return Prop.Aggregate(0, (h, i) => h ^ i.GetHashCode());
Upvotes: 2
Reputation: 4177
Linq contains a useful method to compare collections: 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);
}
Also, implement IEquatable<T>
when you override Equals.
Upvotes: 1
Reputation: 1223
How about something like this:
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;
}
Upvotes: 0