I have a ValueObject
base class like this:
public abstract class ValueObject<T>: IEquatable<T>
{
public abstract bool checkPropertyEquality(T t);
public bool Equals(T? other)
{
if (ReferenceEquals(null, other))
return false;
if (ReferenceEquals(this, other))
return true;
return checkPropertyEquality(other);
}
public override bool Equals(object? obj)
{
if (ReferenceEquals(null, obj))
return false;
if (ReferenceEquals(this, obj))
return true;
if (obj.GetType() != this.GetType())
return false;
return Equals((T)obj);
}
public abstract int GetHashCode();
}
And then I Implemented a class based on this like this:
public class Person : ValueObject<Person>
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Family { get; set; }
public override bool checkPropertyEquality(Person t)
{
return Name == t.Name && Family == t.Family;
}
public override int GetHashCode()
{
return HashCode.Combine(Name, Family);
}
}
So I test this code by running this test scenario and it fails:
[Fact]
public void test1()
{
var list = new List<Person>()
{
new Person { Name = "test1", Family = "testii", Id = Guid.NewGuid() },
new Person { Name = "test2", Family = "testipoor", Id = Guid.NewGuid() },
};
var list1 = new List<Person>()
{
new Person { Name = "test1", Family = "testii", Id = Guid.NewGuid() },
new Person { Name = "test2", Family = "testipoor", Id = Guid.NewGuid() },
};
var e = list.Except(list1).ToList();
var e1 = list1.Except(list).ToList();
Assert.Empty(e1);
Assert.Empty(e);
}
But when I move the IEquatable<>
implementation into the Person
class like this, the test shown above will run successfully:
public abstract class ValueObject<T> //: IEquatable<T>
{
public abstract bool checkPropertyEquality(T t);
}
public class Person : ValueObject<Person>, IEquatable<Person>
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Family { get; set; }
public override bool checkPropertyEquality(Person t)
{
return Name == t.Name && Family == t.Family;
}
public override int GetHashCode()
{
return HashCode.Combine(Name, Family);
}
public bool Equals(Person? other)
{
if (ReferenceEquals(null, other))
return false;
if (ReferenceEquals(this, other))
return true;
return Name == other.Name && Family == other.Family;
}
public override bool Equals(object? obj)
{
if (ReferenceEquals(null, obj))
return false;
if (ReferenceEquals(this, obj))
return true;
if (obj.GetType() != this.GetType())
return false;
return Equals((Person)obj);
}
}
Let's have a look at your compiler warnings:
warning CS0114: 'ValueObject<T>.GetHashCode()' hides inherited member 'object.GetHashCode()'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword.
warning CS0659: 'ValueObject<T>' overrides Object.Equals(object o) but does not override Object.GetHashCode()
GetHashCode
is a virtual method on object
. Defining public abstract int GetHashCode();
does not turn this into an abstract method: it declares a new method on ValueObject<T>
which shadows the method on object
.
When the compiler calls T.GetHashCode
, it calls object
's implementation, not your shadowed version.
What you want is:
public override abstract int GetHashCode();
This does override object
's version and turns it into an abstract method. This version works correctly.
Also: have you come across records?