Любой способ позволить классам, реализующим IEntity и downcast, иметь сравнения операторов ==? - PullRequest
1 голос
/ 16 марта 2009

В основном вот проблема. Все сущности в моей системе идентифицируются по их типу и их 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};

Довольно стандартный сценарий. Поскольку все сущности имеют идентификатор, я определяю интерфейс для всех сущностей.

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

где Customer и Product - что-то вроде

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 по какой-то причине. Я считаю, что это не интуитивно понятно и потенциально проблематично для потребителей моего домена, поскольку им нужно помнить, что все, что происходит в универсальном методе или классе, нужно сравнивать с Equals()

Ответы [ 2 ]

1 голос
/ 16 марта 2009

Хорошо, у меня заняло минуту ... но вот твоя проблема.

Вы, вероятно, делаете что-то подобное, верно?

class Customer : BaseEntity<Customer>{}

class Product : BaseEntity<Product>{}

Видите, проблема в том, что BaseEntity<Customer> и BaseEntity<Product> - это два совершенно разных класса. С помощью шаблонов новый класс генерируется компилятором для каждого шаблонного типа. Другими словами, компилятор выдает что-то вроде BaseEntity_Customer и BaseEntity_Product.

Действительно, я не думаю, что вам вообще нужен интерфейс или шаблоны вообще, не так ли? Если вы просто поместите ID в базовый класс, он будет автоматически доступен для всего, что происходит от BaseEntity. Если вы отметите его как абстрактный, то каждый базовый класс все равно должен будет создать свою собственную реализацию этого ... что, похоже, вы пытаетесь сделать, но на самом деле сработает.

0 голосов
/ 16 марта 2009

Я думаю, что проблема в обновлении со сравнениями дженериков связана с тем, что статические методы и переменные сильно отличаются от методов и переменных экземпляра.

Я не знаю, как к ним относится 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);
}
...