views:

315

answers:

9

Maybe I'm going about this all wrong.

I have a bunch of classes that derive from the "Model" class, a base class with a bunch of common properties and methods. I want them all to implement a set of functionality:

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

Then I implement it in a child class like "Appointment" like 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;
}

This throws an exception "Can't implicitly convert type 'Appointment' to T". If I change the method's signature to "public override Appointment Read(Guid ID)", then the compiler says that I've not implemented the abstract method in the child class.

What am I missing? Can anyone give me some code samples?

+3  A: 

You need to box and cast. I wonder though why this method is generic?

return (T)(object)appointment;
ChaosPandion
I agree, *why is this method generic*?. I don't see the point in `T` if the method is explicitly using `Appointments` and returning an `Appointment` type. Instead of making the method generic, the class should be generic like Greg D recommends, IMO.
dboarman
+1 for the specific answer to why the return issues.
dboarman
+18  A: 

It looks like you could use a generic base class! Consider something like the following:

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

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

Now your subclasses are all strongly typed. Of course, the tradeoff is that you no longer have a single base class. A Model<Appointment> isn't the same thing as a Model<Customer>. I have not generally found this to be a problem, though, because there's little common functionality-- the interfaces are similar, but they all work with different types.

If you'd like a common base, you can certainly cheat and implement an object-based interface that does the same general tasks. E.g., something in the spirit of (untested, but the idea's there):

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 */ }
}
Greg D
+1 - Nice work on this. It's good to know that someone will take the time to examine the root problem when others (me) are too lazy.
ChaosPandion
Agreed. I appreciate you going the extra mile.
Rap
+1  A: 

There is something funky about this design.

Regardless of whether or not the Model class is templated, putting a template parameter on the Read method doesn't make a lot of sense as an instance method.

Usually you'd have something like what Greg D posted.

Jon Seigel
+1 for calling my design funky. :-)
Rap
+7  A: 

Would this work?

public abstract T Read<T>(Guid ID) where T : IAppointment;
Cory Charlton
+1 - You want to use generic constraints as shown in this snippet.
Finglas
No, that doesn't work. You're reasoning about the constraint in the wrong direction; the type parameter constraint constrains the *type argument*. Suppose there were two classes, Appointment and Frob, both of which implement IAppointment. Read<Frob> would be legal because Frob implements IAppointment. But you cannot convert an object of type Appointment to Frob just because they both implement IAppointment!
Eric Lippert
Meant to come back to add more detail. My assumption was that T wasn't being used in the method because of the lack of type constraint and that in adding the constraint the actual implementation could be refactored to return a T object.
Cory Charlton
+1  A: 

If public abstract T Read<T>(Guid ID); of Model will only ever return derived types of Model, consider changing the signature to

public abstract class Model
{
    public abstract void Create();
    public abstract Model Read(Guid ID);  //<--here
    public abstract void Update();
    public abstract void Delete();
}
Dynami Le Savard
+1  A: 

in your Appointment class add this

public class Appointment<T> : Model where T : Appointment
Chris Conway
I had tried that earlier. "Constraints for override and explicit interface implementation methods are inherited from the base method, so they cannot be specified directly." Thanks, though.
Rap
A: 

you can also call: var x = myModel.Read<Appointment>();

m0sa
+3  A: 

You must cast to object first then to T. The reason is rooted in that object is at the top of the inheritance chain. There is no direct correlation from Appointment to T; therefore you have to backtrack to object, then find your way back to T.

I provided this answer to give an explanation of why the return statement would not work unless it was doubly cast -- and in support of the answers given by Chaos & Greg

dboarman
+1 for providing clarity. That helped.
Rap
you're welcome!!!
dboarman
+3  A: 

First, I'd suggest you turn your base class into an interface. If that's an option for you, this will also reduce in slightly less-cluttered code, as you can get rid of the abstract and public keywords in the interface declaration and omit the override in the implementing classes.

Second, as your implementation of Appointment.Read suggests, you could change the method signature of Read to return a model object.

Both suggested changes would result in the following:

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

Third, it seems to me that Read should really be a factory method. In your current code, you need to first instantiate an Appointment object before you can call the Read method to retrieve another Appointment object. This seems wrong to me, from a class design perspective.

How about taking Read out of the base class/interface and providing it as a static method in all derived/implementing classes? For example:

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

You could also consider moving Read into a static (factory) class; however, it would then have to be smart enough to know what kind of object it should return. This would work e.g. if you had a table in your DB that would map a GUID to the corresponding object type.

Edit: The last suggestion above used to be this:

Third, if this is correct so far, the next question would be whether or not Read should be a static method instead. If so, it could be made static and be moved into a static Model class. The method would then act like a factory method that builds IModel objects from a DB:

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