Rap
Rap

Reputation: 7292

C# Generics - How do I return a specific type?

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?

Upvotes: 13

Views: 6016

Answers (9)

Greg D
Greg D

Reputation: 44066

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 */ }
}

Upvotes: 19

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

Upvotes: 2

IAbstract
IAbstract

Reputation: 19881

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

Upvotes: 3

m0sa
m0sa

Reputation: 10940

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

Upvotes: 0

Chris Conway
Chris Conway

Reputation: 16519

in your Appointment class add this

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

Upvotes: 1

Dynami Le Savard
Dynami Le Savard

Reputation: 5036

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

Upvotes: 1

Cory Charlton
Cory Charlton

Reputation: 8938

Would this work?

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

Upvotes: 5

Jon Seigel
Jon Seigel

Reputation: 12401

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.

Upvotes: 1

ChaosPandion
ChaosPandion

Reputation: 78272

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

return (T)(object)appointment;

Upvotes: 5

Related Questions