2010-02-11 5 views
12

Peut-être que je vais à ce sujet tout faux.C# Generics - Comment retourner un type spécifique?

J'ai un tas de classes qui dérivent de la classe "Model", une classe de base avec un tas de propriétés et de méthodes communes. Je veux tous à mettre en œuvre un ensemble de fonctionnalités:

public abstract void Create(); 
public abstract T Read<T>(Guid ID); //<--Focus on this one 
public abstract void Update(); 
public abstract void Delete(); 

Puis je mettre en œuvre dans une classe d'enfants comme « Rendez-vous » comme ceci:

public override T Read<T>(Guid ID) 
{ 
    var appt = db.Appointments.First(a => a.AppointmentID.Equals(ID)); 
    var appointment = new Appointment() 
    { 
    DateEnd = appt.dateEnd.GetValueOrDefault(), 
    Location = appt.location, 
    Summary = appt.summary 
    }; 
return appointment; 
} 

Cela jette une exception « peut pas convertir implicitement tapez "rendez-vous" à T ". Si je change la signature de la méthode en "public override Appointment Read (Guid ID)", le compilateur dit que je n'ai pas implémenté la méthode abstraite dans la classe enfant.

Qu'est-ce qui me manque? Quelqu'un peut-il me donner des échantillons de code?

+5

Quelle est la motivation derrière avoir votre méthode Read générique? –

+0

+1 J'aime la question car j'essayais de faire quelque chose de similaire. Puis réalisé que je le faisais mal - fini avec une solution très similaire à celle de Greg. Si cette question avait été posée il y a 4 jours, je n'aurais pas passé autant de temps à essayer de comprendre ce que je faisais de mal. – IAbstract

+3

À quoi bon utiliser des génériques si vous n'utilisez pas du tout T dans votre méthode et si vous voulez retourner un rendez-vous? – jason

Répondre

19

Il semble que vous pourriez utiliser une classe de base générique! Pensez à quelque chose comme ce qui suit:

class Model<T> 
{ 
    public abstract T Read(Guid ID); 
} 

class Appointment : Model<Appointment> 
{ 
    public override Appointment Read(Guid ID) { } 
} 

Maintenant, vos sous-classes sont tous fortement typés. Bien sûr, le compromis est que vous n'avez plus une seule classe de base. Un Model<Appointment> n'est pas la même chose qu'un Model<Customer>. Cependant, je n'ai généralement pas trouvé cela à un problème, car il y a peu de fonctionnalités communes - les interfaces sont similaires, mais ils fonctionnent tous avec des types différents.

Si vous souhaitez une base commune, vous pouvez certainement tricher et implémenter une interface basée sur object qui effectue les mêmes tâches générales. Par exemple, quelque chose dans l'esprit de (non testé, mais l'idée est là):

interface IModelEntity 
{ 
    object Read(Guid ID); 
} 

class Model<T> : IModelEntity 
{ 
    public T Read(Guid ID) 
    { 
     return this.OnRead(ID); // Call the abstract read implementation 
    } 

    object IModelEntity.Read(Guid ID) 
    { 
     return this.OnRead(ID); // Call the abstract read implementation 
    } 

    protected abstract virtual T OnRead(Guid ID); 
} 

class Appointment : Model<Appointment> 
{ 
    protected override Appointment OnRead(Guid ID) { /* Do Read Stuff */ } 
} 
+0

+1 - Beau travail sur ce sujet.Il est bon de savoir que quelqu'un prendra le temps d'examiner le problème racine quand les autres (moi) sont trop paresseux. – ChaosPandion

+0

D'accord. J'apprécie que vous alliez le mile supplémentaire. – Rap

4

Vous devez encadrer et lancer. Je me demande bien pourquoi cette méthode est générique?

return (T)(object)appointment; 
+2

Je suis d'accord, * pourquoi cette méthode générique * ?. Je ne vois pas le point dans 'T' si la méthode utilise explicitement' Appointments' et retourne un type 'Appointment'. Au lieu de rendre la méthode générique, la classe devrait être générique comme le recommande Greg D, IMO. – IAbstract

+0

+1 pour la réponse spécifique à pourquoi les problèmes de retour. – IAbstract

1

Il y a quelque chose au sujet de cette conception géniale.

Que la classe Model soit ou non basée sur des modèles, la définition d'un paramètre de modèle sur la méthode Read n'a pas beaucoup de sens en tant que méthode d'instance.

Habituellement, vous auriez quelque chose comme ce que Greg D posté.

+0

+1 pour appeler ma conception funky. :-) – Rap

5

Est-ce que cela fonctionnerait?

public abstract T Read<T>(Guid ID) where T : IAppointment; 
+0

+1 - Vous souhaitez utiliser les contraintes génériques comme indiqué dans cet extrait. – Finglas

+2

Non, cela ne fonctionne pas. Vous raisonnez sur la contrainte dans la mauvaise direction; la contrainte de paramètre de type contraint l'argument * type *. Supposons qu'il y ait deux classes, Rendez-vous et Frob, qui implémentent IAppointment. Lire serait légal car Frob implémente IAppointment. Mais vous ne pouvez pas convertir un objet de type Appointment en Frob juste parce qu'ils implémentent IAppointment! –

1

Si public abstract T Read<T>(Guid ID); de Model ne fera que jamais RETOURNER dérivés types de Model, pensez à modifier la signature à

public abstract class Model 
{ 
    public abstract void Create(); 
    public abstract Model Read(Guid ID); //<--here 
    public abstract void Update(); 
    public abstract void Delete(); 
} 
1

dans votre classe Rendez-vous ajouter cette

public class Appointment<T> : Model where T : Appointment 
+0

J'avais déjà essayé ça plus tôt. "Les contraintes de substitution et les méthodes d'implémentation d'interface explicite sont héritées de la méthode de base, elles ne peuvent donc pas être spécifiées directement." Merci quand même. – Rap

0

vous pouvez aussi appeler: var x = myModel.Read<Appointment>();

3

Vous devez d'abord passer à object puis à T. La raison en est que object est en haut de la chaîne d'héritage.Il n'y a pas de corrélation directe entre Appointment et T; Par conséquent, vous devez revenir à object, puis retrouver votre chemin vers T.

Je fourni cette réponse pour donner une explication des raisons pour lesquelles la déclaration de retour ne fonctionnerait pas à moins qu'il a été doublement jeté - et à l'appui des réponses données par le Chaos & Greg

+0

+1 pour plus de clarté. Cela a aidé. – Rap

+0

vous êtes les bienvenus! – IAbstract

2

Première, je vous suggère vous transformez votre classe de base en une interface. Si c'est une option pour vous, cela réduira également dans un code légèrement moins encombré, car vous pouvez vous débarrasser des mots-clés abstract et public dans la déclaration d'interface et omettre le override dans les classes d'implémentation.

Deuxième, comme la mise en œuvre de Appointment.Read suggère, vous pouvez changer la signature de la méthode de Read pour retourner un objet modèle.

deux changements suggérés seraient les résultats suivants:

public interface IModel 
{ 
    void Create(); 
    IModel Read(Guid ID); 
    void Update(); 
    void Delete(); 
} 

Troisième, il me semble que Read devrait vraiment être une méthode de fabrication. Dans votre code actuel, vous devez d'abord instancier un objet Appointment avant de pouvoir appeler la méthode Read pour extraire un autre objet Appointment. Cela me semble faux, du point de vue de la conception de classe.

Que diriez-vous de prendre Read hors de la classe/interface de base et de l'utiliser comme méthode statique dans toutes les classes dérivées/implémentées? Par exemple:

public class Appointment : IModel 
{ 
    public static Appointment Read(Guid ID) 
    { 
     return new Appointment() 
       { 
        ... 
       }; 
    } 
} 

Vous pouvez également envisager de déplacer Read dans une classe statique (usine); Cependant, il devrait alors être assez intelligent pour savoir quel genre d'objet il devrait retourner. Cela fonctionnerait par exemple. si vous aviez une table dans votre base de données qui mapperait un GUID au type d'objet correspondant.

Modifier: La dernière suggestion ci-dessus utilisé comme ceci:

Troisièmement, si cela est correct jusqu'à présent, la question suivante serait ou non Read devrait être une méthode statique à la place. Si c'est le cas, il peut être remplacé par static et être déplacé dans une classe Model statique. La méthode serait alors agir comme une méthode de fabrication qui construit IModel objets à partir d'un DB:

Guid guid = ...; 
IModel someModel = Model.Read(guid);