是否允许实现IEntity和downcast的类具有operator ==比较?

时间:2009-03-16 04:51:02

标签: c# inheritance operators equals-operator

基本上就是这个问题。我系统中的所有实体都按其类型和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};

非常标准的场景。由于所有实体都有一个Id,我为所有实体定义了一个接口。

public interface IEntity {
  int Id { get; set;}
}

为了简化我制作的实体的创建:

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);
  }
}

客户和产品类似

public class Customer : BaseEntity<Customer>, IEntity {}
public class Product : BaseEntity<Product>, IEntity {}

我认为这是一个笨拙的海鲂。我认为我所要做的就是在每个实体中覆盖Equals(如果我超级聪明,我甚至可以在BaseEntity中覆盖它一次)以及所有工作。

所以现在我正在扩展我的测试覆盖率并发现它不那么简单!首先,当向下转换为IEntity并使用==时,不使用BaseEntity<>覆盖。

那么解决方案是什么?我还能做些什么吗?如果没有,这是非常烦人的。

更新1 我的测试似乎有问题 - 或者更确切地说是在泛型上进行比较。看看这个:

[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);            
}

这是怎么回事?这并不像我担心的那么大,但仍然非常烦人,并且这种语言完全不直观。

更新2 啊我明白了,由于某种原因,通用隐式向下转发IEntity。我认为这对于我的Domain的消费者来说是不直观的并且可能存在问题,因为他们需要记住在通用方法或类中发生的任何事情需要与Equals()进行比较

2 个答案:

答案 0 :(得分:1)

好的,花了我一分钟......但这是你的问题。

你可能正在做这样的事情,对吧?

class Customer : BaseEntity<Customer>{}

class Product : BaseEntity<Product>{}

请注意,问题是BaseEntity<Customer>BaseEntity<Product>是两个完全不同的类。使用模板,编译器会为每个模板化类型生成一个新类。换句话说,编译器将会发布的内容类似于BaseEntity_Customer和BaseEntity_Product。

真的,我觉得你根本不需要界面或模板吗?如果您只是将ID放在基类中,那么它将自动存在于BaseEntity派生的任何内容中。如果你把它标记为抽象,那么每个基类仍然需要创建它们自己的实现...这就是你想要做的事情,但实际上会有效。

答案 1 :(得分:0)

我认为更新中的问题与泛型的比较与静态方法和变量与实例方法和变量完全不同的事实有关。

我不知道CLR如何对待它们,但从概念上讲它们几乎就像两个不同的类。因此,就像您无法访问T上的任何静态方法一样,T上的运算符也不会被应用。

这是我对这个问题的理解。如果有人拥有它,我会喜欢更技术性的解释。

此外,至少在一个方面,这个问题没有实际意义。如果IEntity是泛型参数T的值,编译器将不允许您使用==运算符比较类型T的两个实例。我相信这是因为我上面所说的。

但是,如果泛型参数的类型为class,IEntity或IEntity是实例参数,则问题仍然存在。例如

[Test]
public void are_equal_when_passed_as_parameters_downcast_to_interfaces() {
    //FAILS!
    CompareTwoEntities(new Terminal() { Id = 1 }, new Terminal() { Id = 1 });
}
private void CompareTwoEntities(IEntity e1, IEntity e2) {
    Assert.IsTrue(e1 == e2);
}