2010-06-26 16 views
0

TL; DR:méthode Redéfinir type de retour en classe dérivée sans génériques

Est-il possible d'ajouter une méthode abstraite à une classe de base qui permet aux classes dérivées de passer outre le type de retour de la méthode, sans utilisation de génériques, et sans l'utilisation du mot-clé new?


Je travaille sur le développement de certains modèles personnalisés pour LLBLGen Pro. Dans le processus, je refuse de modifier les modèles par défaut proposés par LLBLGen Pro, de sorte que mon approche ne remplace pas les fichiers d'autres personnes s'ils choisissent d'implémenter mes modèles. Une tâche sur laquelle j'ai commencé à travailler (et qui a bien progressé vers) consiste à développer un modèle qui génère un DTO pour chaque entité. Dans ce sens, un objectif est de fournir à mes entités une méthode ToDTO(). Dans l'intérêt de la programmation générique, j'ai décidé de définir cette méthode au sein d'une classe de base commune, et c'est là que mes problèmes commencent.


Gardez à l'esprit que le but de définir la méthode ToDTO() dans la classe de base est parce que je suis à la recherche de créer un référentiel générique (avec une méthode Fetch(), par exemple) que je voudrais avoir travailler sur le CommonEntityBase, par opposition à une entité spécifique.


LLBLGen définit sa CommonEntityBase classe comme ceci:

public abstract partial class CommonEntityBase : EntityBase2 { 
    // LLBLGen-generated code 
} 

Mon plan initial était d'ajouter ma méthode à une autre classe partielle comme ceci:

public abstract partial class CommonEntityBase { 
    public abstract CommonDTOBase ToDto(); 
} 

Je pensais que les hérita Les classes seraient capables de définir le type de retour dans leurs méthodes comme un type dérivé du type de retour de la classe de base, comme ceci:

public partial class PersonEntity : CommonEntityBase { 
    public override PersonDTO ToDto(){ return new PersonDTO(); } 
} 

mais I was wrong.


Ma deuxième tentative a été de définir la classe en utilisant les génériques, comme tel:

public abstract partial class CommonEntityBase<T> : CommonEntityBase 
     where T : CommonDTOBase { 
    public abstract T ToDto(); 
} 

assez simple. Tout ce que je dois faire est de faire hériter mes classes d'entités générées de cette nouvelle base d'entité. Juste une mise en garde. Comme je ne veux pas écraser les modèles de LLBLGen, je reviens à des classes partielles.

entités individuelles de LLBLGen ont cette définition:

public partial class PersonEntity : CommonEntityBase { 
    // LLBLGen-generated code 
} 

Et c'est là mon problème.Pour ma méthode de travail, je dois créer ma propre classe partielle avec cette définition:

public partial class PersonEntity : CommonEntityBase<PersonDTO> { 
    public override PersonDTO ToDto(){ return new PersonDTO(); } 
} 

Bien sûr, cela est impossible, parce que, as I now know,

Toutes les les parties [d'une classe partielle] qui spécifient une classe de base doivent être d'accord, mais les parties qui omettent une classe de base héritent toujours du type de base.


La troisième chose que je vais essayer était primordial simplement la définition de la fonction de la classe de base avec le mot-clé new:

public abstract partial class CommonEntityBase { 
    public virtual CommonDTOBase ToDto(){ return null; } 
} 

public partial class PersonEntity : CommonEntityBase { 
    public new PersonDTO ToDto(){ return new PersonDTO(); } 
} 

Cependant, ce défaites le but de mon approche complètement, comme je vous voulez pouvoir accéder à la méthode ToDTO() de PersonEntity lorsqu'elle est lancée en tant que CommonEntityBase. Avec cette approche, faire:

CommonEntityBase e = new PersonEntity(); 
var dto = e.ToDto(); 

entraînerait dto étant nul, que je ne veux pas.


Je suis venu à travers variouslinks discuter de ma première approche, et pourquoi il ne fonctionne pas, et pointant généralement à mon approche générique en tant que solution dans le sens général. Cependant, dans ma situation, les génériques ne semblent pas fonctionner. Tout cela pour me demander si ce que j'essaie d'accomplir est possible ou non. Existe-t-il un moyen d'ajouter une méthode abstraite à une classe de base qui permet aux classes dérivées de remplacer le type de retour de la méthode, sans l'utilisation de génériques, et sans l'utilisation du mot-clé new? Ou peut-être que j'aborde ce point du mauvais angle, et il y a une autre technique qui pourrait résoudre mes problèmes?


EDIT

est ici un cas d'utilisation pour ce que je voudrais accomplir avec les entités, en Porges's approche:

public class BaseRepository<D,E> where D : CommonDTOBase where E : CommonEntityBase,new 
    public D Get(Guid id){ 
     var entity = new E(); 
     entity.SetId(id); 

     // LLBLGen adapter method; populates the entity with results from the database 
     FetchEntity(entity); 

     // Fails, as entity.ToDto() returns CommonDTOBase, not derived type D 
     return entity.ToDto(); 
    } 
} 
+0

Vous semblez avoir quelques problèmes conceptuels avec les classes 'partial'. Il n'y a pas de magie en jeu; Je pense que la meilleure façon d'y penser est que le compilateur insère tout le code défini pour une classe partielle dans une définition de classe avant de compiler. – porges

+0

Google "types de retour covariant". –

Répondre

0

Je ne sais pas LLBLGen , mais je crois que vous pourriez résoudre votre problème de cette façon, en introduisant une interface pour contenir le paramètre de type:

public interface DTOProvider<T> where T : CommonDTOBase { 
    public T ToDTO(); 
} 

Et pour vos classes d'entités, procédez comme suit:

public partial class PersonEntity : CommonEntityBase, DTOProvider<PersonDTO> { 
    public PersonDTO ToDto() { return new PersonDTO(); } 
} 

Parce que les classes partielles peuvent introduire des interfaces différentes, cela fonctionne.La seule tristesse est qu'un casting est nécessaire pour obtenir l'accès à la méthode par le type de base:

public void DoSomethingWithDTO<T>(CommonBaseEntity entity) 
     where T : CommonDTOBase { 
    T dto = ((DTOProvider<T>) entity).ToDTO(); 
    ... 
} 

Bien sûr, vous pouvez appeler ToDTO directement sans la distribution lorsque vous avez une référence d'un des types d'entité dérivée :

public void DoSomethingWithPersonDTO(PersonEntity entity) 
{ 
    PersonDTO dto = entity.ToDTO(); 
    ... 
} 

Si vous utilisez .NET Framework 4, vous pouvez utiliser la variance générique pour rendre l'interface DTOProvider plus facile à utiliser à partir du code qui se soucie seulement de travailler avec CommonDTOBase en déclarant la covariant type DTO:

public interface DTOProvider<out T> where T : CommonDTOBase { 
    public T ToDTO(); 
} 

(Notez le « out ».) Ensuite, votre méthode DoSomethingWithDTO n'a pas besoin du paramètre de type:

public void DoSomethingWithDTO(CommonBaseEntity entity) { 
    CommonDTOBase dto = ((DTOProvider<CommonDTOBase>) entity).ToDTO(); 
    ... 
} 

Il est tentant d'essayer de déclarer : CommonBaseEntity, DTOProvider<T> la classe partielle CommonBaseEntity. Malheureusement, cela ne fonctionne pas, car lorsque les définitions partielles sont fusionnées, le paramètre type est reporté et votre type CommonBaseEntity finit par être un type générique, ce qui semble être ce qui vous a amené à une liaison en premier lieu.

+0

Avec cette approche, d'où 'CommonBaseEntity' obtient-il sa définition de' ToDto() '? S'il hérite en utilisant ': DTOProvider ', alors nous rencontrons mon problème initial de ne pas pouvoir changer les types de retour, et s'il hérite en utilisant ': DTOProvider ', nous devons incorporer des génériques dans 'CommonBaseEntity'. – cmptrgeekken

4

Au lieu de:

public abstract partial class CommonEntityBase { 
    public abstract CommonDTOBase ToDto(); 
} 

public partial class PersonEntity : CommonEntityBase { 
    public override PersonDTO ToDto(){ return new PersonDTO(); } 
} 

Pourquoi n'êtes-vous pas juste un DTO retournaient comme ceci:

public abstract partial class CommonEntityBase { 
    public abstract CommonDTOBase ToDto(); 
} 

public partial class PersonEntity : CommonEntityBase { 
    // changed PersonDTO to CommonDTOBase 
    public override CommonDTOBase ToDto(){ return new PersonDTO(); } 
} 

Je pense que ce plus idiomatiques pour le code OO. Y at-il une raison pour laquelle vous devez connaître le type exact du DTO?