Any way to allow classes implementing IEntity and downcast to have operator == comparisons?
- by George Mauer
Basically here's the issue. All entities in my system are identified by their type and their id.
new Customer() { Id = 1} == new Customer() {Id = 1};
new Customer() { Id = 1} != new Customer() {Id = 2};
new Customer() { Id = 1} != new Product() {Id = 1};
Pretty standard scenario. Since all Entities have an Id I define an interface for all entities.
public interface IEntity {
int Id { get; set;}
}
And to simplify creation of entities I make
public abstract class BaseEntity<T> : where T : IEntity {
int Id { get; set;}
public static bool operator ==(BaseEntity<T> e1, BaseEntity<T> e2) {
if (object.ReferenceEquals(null, e1)) return false;
return e1.Equals(e2);
}
public static bool operator !=(BaseEntity<T> e1, BaseEntity<T> e2) {
return !(e1 == e2);
}
}
where Customer and Product are something like
public class Customer : BaseEntity<Customer>, IEntity {}
public class Product : BaseEntity<Product>, IEntity {}
I think this is hunky dory. I think all I have to do is override Equals in each entity (if I'm super clever, I can even override it only once in the BaseEntity) and everything with work.
So now I'm expanding my test coverage and find that its not quite so simple! First of all , when downcasting to IEntity and using == the BaseEntity< override is not used.
So what's the solution? Is there something else I can do? If not, this is seriously annoying.
Upadate It would seem that there is something wrong with my tests - or rather with comparing on generics. Check this out
[Test] public void when_created_manually_non_generic() {
// PASSES!
var e1 = new Terminal() {Id = 1};
var e2 = new Terminal() {Id = 1};
Assert.IsTrue(e1 == e2);
}
[Test] public void when_created_manually_generic() {
// FAILS!
GenericCompare(new Terminal() { Id = 1 }, new Terminal() { Id = 1 });
}
private void GenericCompare<T>(T e1, T e2) where T : class, IEntity {
Assert.IsTrue(e1 == e2);
}
Whats going on here? This is not as big a problem as I was afraid, but is still quite annoying and a completely unintuitive way for the language to behave.
Update Update Ah I get it, the generic implicitly downcasts to IEntity for some reason. I stand by this being unintuitive and potentially problematic for my Domain's consumers as they need to remember that anything happening within a generic method or class needs to be compared with Equals()