2009-03-16 20 views
1

Fondamentalement, voici le problème. Toutes les entités de mon système sont identifiées par leur type et leur id.Une façon d'autoriser les classes implémentant IEntity et downcast à avoir des opérateurs == comparaisons?

new Customer() { Id = 1} == new Customer() {Id = 1}; 
new Customer() { Id = 1} != new Customer() {Id = 2}; 
new Customer() { Id = 1} != new Product() {Id = 1}; 

Scénario plutôt classique. Comme toutes les entités ont un identifiant, je définis une interface pour toutes les entités.

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

Et pour simplifier la création d'entités je fais:

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

où la clientèle et le produit sont quelque chose comme

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

Je pense que c'est Hunky Dory. Je pense que tout ce que j'ai à faire est de remplacer Equals dans chaque entité (si je suis super intelligent, je peux même le remplacer une seule fois dans le BaseEntity) et tout le travail.

Alors maintenant, j'élargis ma couverture de test et je trouve que ce n'est pas si simple! Tout d'abord, lors du downcasting à IEntity et en utilisant == le remplacement BaseEntity<> n'est pas utilisé.

Alors, quelle est la solution? Y a-t-il autre chose que je puisse faire? Sinon, c'est vraiment ennuyeux.

Mise à jour 1 Il semblerait qu'il y ait quelque chose qui cloche avec mes tests - ou plutôt avec des comparatifs sur les génériques. Vérifiez cela:

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

Que se passe-t-il ici? Ce problème n'est pas aussi grave que je le craignais, mais il est toujours assez énervant et complètement inintéressant pour le comportement de la langue.

Mise à jour 2 Ah Je comprends, les génériques implicitement downcasts à IEntity pour une raison quelconque. Je maintiens ceci étant non intuitif et potentiellement problématique pour les consommateurs de mon domaine car ils doivent se rappeler que tout ce qui se passe dans une méthode générique ou une classe doit être comparé avec Equals()

+0

Est-client ou mettre en œuvre IEntity héritent de BaseEntity ? Vous avez du mal à déduire la connexion entre vos exemples concrets et le code de base. –

+0

Il fait les deux. BaseEntity <> est une classe abstraite d'aide utilisée en option pour aider à créer des entités, IEntity est ce que toute personne qui s'intéresse à autre chose que la création d'objet devrait utiliser. Le meilleur moyen que j'ai trouvé de garder mes hiérarchies flexibles tout en faisant DRY. –

+0

Pourriez-vous ajouter dans les déclarations de classe pour le client et le produit? Cela rendrait cela plus facile à comprendre, je pense ... – CodeRedick

Répondre

1

Ok, m'a pris une minute ... mais voici votre problème .

Vous faites probablement quelque chose comme ça, n'est-ce pas?

class Customer : BaseEntity<Customer>{} 

class Product : BaseEntity<Product>{} 

Voir, le problème est que BaseEntity<Customer> et BaseEntity<Product> sont deux classes complètement différentes. Avec les modèles, une nouvelle classe est générée par le compilateur pour chaque type de modèle. En d'autres termes, ce que le compilateur va expulser est quelque chose comme BaseEntity_Customer et BaseEntity_Product. Vraiment, je ne pense pas que vous ayez besoin de l'interface ou des modèles du tout? Si vous mettez juste ID dans la classe de base, il sera automatiquement là pour tout ce qui dérive de BaseEntity. Si vous le marquez de manière abstraite, chaque classe de base devra toujours créer sa propre implémentation ... ce qui semble être ce que vous essayez de faire, mais qui fonctionnerait réellement.

+0

L'interface est là parce que je ne veux pas forcer toutes mes entités à hériter de BaseEntity <> Je veux garder ma hiérarchie aussi simple que possible pour le consommateur, sans me répéter. En fait, j'aimerais qu'il y ait un moyen de marquer BaseEntity comme invisible en dehors de la bibliothèque. –

+0

En ce qui concerne le générique, c'est simplement parce que je pense à faire des choses de réflexion dans l'entité de base pour mettre en place certaines conventions. Je ne vois pas pourquoi BaseEntity_Customer et BaseEntity_Product me donneraient les résultats actuels –

+0

Ils vous donnent les résultats actuels car ce sont des classes différentes. Pas le même cours. En d'autres termes, le client hérite de BE_Customer, pas de BaseEntity. En fait, il n'existe pas de BaseEntity. – CodeRedick

0

Je pense que le problème dans la mise à jour avec des comparaisons de génériques a à voir avec le fait que les méthodes statiques et les variables sont très différentes des méthodes d'instance et des variables.

Je ne sais pas comment le CLR les traite mais conceptuellement ils sont presque comme deux classes différentes. Donc, tout comme vous ne seriez pas en mesure d'accéder à des méthodes statiques sur T, un opérateur sur T ne serait pas appliqué.

C'est ma compréhension du problème tel qu'il est. J'adorerais une explication plus technique si quelqu'un l'a.

En outre, au moins sur un front, le problème est discutable. Si IEntity est la valeur du paramètre générique T, le compilateur ne vous permettra pas de comparer deux instances de type T en utilisant l'opérateur ==. Je crois que c'est à cause de ce que j'ai dit plus haut.

Cependant, le problème persiste si le paramètre générique est de type class, IEntity ou si IEntity est un paramètre d'instance. Par exemple

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