1

基本的にここに問題があります。システム内のすべてのエンティティは、タイプと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);
  }
}

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()

4

2 に答える 2

1

わかりました、少し時間がかかりました... しかし、ここに問題があります。

あなたはおそらくこのようなことをしていますよね?

class Customer : BaseEntity<Customer>{}

class Product : BaseEntity<Product>{}

問題は、BaseEntity<Customer>BaseEntity<Product>が 2 つのまったく異なるクラスであることです。テンプレートを使用すると、テンプレート化された型ごとにコンパイラによって新しいクラスが生成されます。つまり、コンパイラが追い出すのは、BaseEntity_Customer と BaseEntity_Product のようなものです。

本当に、インターフェイスやテンプレートはまったく必要ないと思いますか? 基本クラスに ID を入れるだけで、BaseEntity から派生するすべてのものに対して自動的に ID が存在します。抽象化すると、各基本クラスは独自の実装を作成する必要があります...これは、あなたがやろうとしているように見えますが、実際には機能します。

于 2009-03-16T05:15:43.387 に答える
0

ジェネリックの比較に関する更新の問題は、静的メソッドおよび変数がインスタンス メソッドおよび変数とはまったく異なるという事実に関係していると思います。

CLR がそれらをどのように扱うかはわかりませんが、概念的には 2 つの異なるクラスのようです。したがって、T の静的メソッドにアクセスできないのと同じように、T の演算子は適用されません。

それが問題の私の理解です。誰かがそれを持っていれば、より技術的な説明が欲しいです。

さらに、少なくとも 1 つの面では、問題は議論の余地があります。IEntity がジェネリック パラメーター T の値である場合、コンパイラは == 演算子を使用して T 型の 2 つのインスタンスを比較することを許可しません。これは、上で述べたことのおかげだと思います。

ただし、ジェネリック パラメータの型が 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);
}
于 2009-03-16T05:50:31.760 に答える