2010-02-11 12 views
12

Vielleicht mache ich das alles falsch.C# Generika - Wie gebe ich einen bestimmten Typ zurück?

Ich habe eine Reihe von Klassen, die von der "Model" -Klasse, eine Basisklasse mit einer Reihe von gemeinsamen Eigenschaften und Methoden ableiten. Ich möchte, dass sie alle eine Reihe von Funktionen implementieren:

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

ich es dann umzusetzen in einer Kindklasse wie „Termin“ in etwa so:

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

Diese eine Ausnahme „Kann nicht implizit wirft konvertieren Geben Sie "Termin" bis "T" ein. Wenn ich die Signatur der Methode in "public override Appointment Read (Guid ID)" ändere, sagt der Compiler, dass ich die abstrakte Methode in der Kindklasse nicht implementiert habe.

Was fehlt mir? Kann mir jemand ein paar Codebeispiele geben?

+5

Was ist die Motivation hinter mit Ihrer Read-Methode generisch? –

+0

+1 Ich mag die Frage, als ich versuchte, etwas ähnliches zu tun. Dann erkannte ich, dass ich es falsch gemacht hatte - endete mit einer Lösung, die Greg sehr ähnlich war. Wenn diese Frage vor 4 Tagen hier gewesen wäre, hätte ich nicht so viel Zeit damit verbracht, herauszufinden, was ich falsch mache. – IAbstract

+3

Was ist der Sinn der Verwendung von Generika, wenn Sie in Ihrer Methode überhaupt nicht 'T' verwenden und einen' Termin' zurückgeben wollen? – jason

Antwort

19

Es sieht so aus, als könnten Sie eine generische Basisklasse verwenden! Betrachten Sie etwas wie das folgende:

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

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

Jetzt sind Ihre Unterklassen alle stark getippt. Der Nachteil ist natürlich, dass Sie keine einzige Basisklasse mehr haben. A Model<Appointment> ist nicht dasselbe wie Model<Customer>. Im Allgemeinen habe ich dies jedoch nicht als ein Problem empfunden, da es wenig gemeinsame Funktionen gibt - die Schnittstellen sind ähnlich, aber sie arbeiten alle mit unterschiedlichen Typen.

Wenn Sie eine gemeinsame Basis möchten, können Sie sicherlich eine object-basierte Schnittstelle betrügen und implementieren, die die gleichen allgemeinen Aufgaben ausführt. Zum Beispiel, etwas im Sinne der (nicht getestet, aber die Idee ist da):

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 - Schöne Arbeit daran.Es ist gut zu wissen, dass sich jemand die Zeit nimmt, das Grundproblem zu untersuchen, wenn andere (mich) zu faul sind. – ChaosPandion

+0

Einverstanden. Ich weiß es zu schätzen, dass Sie die Extrameile gehen. – Rap

4

Sie müssen boxen und werfen. Ich frage mich jedoch, warum diese Methode generisch ist?

return (T)(object)appointment; 
+2

Ich stimme zu, * warum ist diese Methode generisch * ?. Ich sehe den Punkt in "T" nicht, wenn die Methode explizit "Termine" verwendet und einen "Termin" -Typ zurückgibt. Anstatt die Methode generisch zu machen, sollte die Klasse generisch sein wie Greg D empfiehlt, IMO. – IAbstract

+0

+1 für die spezifische Antwort, warum die Rückkehr Probleme. – IAbstract

1

Es ist etwas flippiger über diesen Entwurf.

Unabhängig davon, ob die Klasse eine Templates ist oder nicht, macht das Setzen eines Template Parameters auf die Read Methode als Instanzmethode keinen großen Sinn.

Normalerweise hätten Sie etwas, was Greg D gepostet hat.

+0

+1 für den Aufruf mein Design funky. :-) – Rap

5

Würde das funktionieren?

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

+1 - Sie möchten generische Constraints wie in diesem Snippet gezeigt verwenden. – Finglas

+2

Nein, das funktioniert nicht. Sie argumentieren über die Einschränkung in die falsche Richtung; Die Einschränkung des Typparameters beschränkt das Argument * des Typs *. Angenommen, es gibt zwei Klassen, Appointment und Frob, die IAppointment implementieren. Read wäre legal, weil Frob IAppointment implementiert. Aber Sie können ein Objekt vom Typ Appointment nicht in Frob konvertieren, nur weil beide IAppointment implementieren! –

1

Wenn public abstract T Read<T>(Guid ID); von Model wird immer nur Typen von Model abgeleitete Rückkehr betrachtet die Signatur

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

in Ihrer Terminklasse fügen Sie dieses

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

Ich hatte das früher versucht. "Constraints für Override- und explizite Interface-Implementierungsmethoden werden von der Basismethode geerbt, so dass sie nicht direkt angegeben werden können." Trotzdem danke. – Rap

0

Sie können auch anrufen: var x = myModel.Read<Appointment>();

3

Sie müssen zunächst object und dann T eingeben. Der Grund dafür liegt darin, dass an der Spitze der Vererbungskette steht.Es gibt keine direkte Korrelation von Appointment zu T; Sie müssen daher zurück zu object gehen, dann finden Sie Ihren Weg zurück zu T.

vorausgesetzt ich diese Antwort eine Erklärung, warum die return-Anweisung funktionieren würde zu geben, es sei denn, es doppelt gegossen wurde - und zur Unterstützung der von Chaos & Greg gegebenen Antworten

+0

+1 für Klarheit. Das hat geholfen. – Rap

+0

Gern geschehen !!! – IAbstract

2

Erste, würde ich vorschlagen Sie verwandeln Ihre Basisklasse in eine Schnittstelle. Wenn dies eine Option für Sie ist, wird dies auch in etwas weniger überladenen Code reduzieren, wie Sie die abstract und public Schlüsselwörter in der Schnittstellendeklaration loswerden und die override in den implementierenden Klassen weglassen können.

Zweite, wie Ihre Implementierung von Appointment.Read schlägt vor, die Methode Signatur von Read ändern könnte ein Modellobjekt zurückzukehren.

Beide vorgeschlagenen Änderungen in der folgenden führen würde:

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

Third, scheint es mir, dass Read wirklich ein Factory-Methode sein sollte. In Ihrem aktuellen Code müssen Sie zunächst ein Appointment Objekt instanziieren, bevor Sie die Methode Read aufrufen können, um ein weiteres Appointment Objekt abzurufen. Dies scheint mir aus Sicht des Klassendesigns falsch zu sein.

Wie wäre es mit Read aus der Basisklasse/Schnittstelle und Bereitstellung als statische Methode in allen abgeleiteten/implementierenden Klassen? Zum Beispiel:

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

Sie könnten auch erwägen Read in eine statischen (Fabrik) Klasse zu bewegen; Allerdings müsste es dann schlau genug sein zu wissen, welche Art von Objekt es zurückgeben soll. Dies würde z.B. wenn Sie in Ihrer DB eine Tabelle hätten, die dem entsprechenden Objekttyp eine GUID zuordnen würde.

bearbeitet: Der letzte Vorschlag oben verwendet, dies zu sein:

Drittens, wenn dies so weit richtig ist, wäre die nächste Frage, ob oder nicht Read sollte stattdessen eine statische Methode sein. Wenn ja, könnte es static gemacht werden und in eine statische Model Klasse verschoben werden. Das Verfahren würde wirkt dann wie eine Factory-Methode, die IModel Objekte aus einer DB baut:

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