Adam Levitt
Adam Levitt

Reputation: 10476

C# Factory Method with Generic Cast to Interface

I have the following classes:

// -- model hierarchy
public interface IJob {
}

public abstract class AbstractJob : IJob {
}

public class FullTimeJob : AbstractJob {               
}

// -- dao hierarchy
public interface IJobDao<T> where T : IJob {       
  T findById(long jobId);
  long insert(T job);
}

public interface IFullTimeJobDao : IJobDao<FullTimeJob> {        
}

public abstract class AbstractDao {    
}

public abstract class AbstractJobDaoImpl<T> : AbstractDao, IJobDao<T> where T : IJob {
  public T findById(long jobId) {
    // omitted for brevity
  }

  public long insert(T job) {
    // omitted for brevity
  }
}

public class FullTimeJobDaoImpl : AbstractJobDaoImpl<FullTimeJob>, IFullTimeJobDao {
}

I'm calling the following code from a factory method, which does not seem to work:

public IJobDao<IJob> createJobDao(long jobDaoTypeId)
{
    object jobDao = Activator.CreateInstance(typeof(FullTimeJobDaoImpl));
    return jobDao as IJobDao<IJob>; // <-- this returns null
    return (IJobDao<IJob>) jobDao; // <-- this cast fails
}

How is this "up cast" properly achieved?

Upvotes: 3

Views: 2878

Answers (3)

horgh
horgh

Reputation: 18534

For this cast to be possible you'll need to mark the interface type parameter as out:

public interface IJobDao<out T> where T : IJob {...}

Then

object jobDao = Activator.CreateInstance(typeof(FullTimeJobDaoImpl));
var r = jobDao as IJobDao<IJob>; //not null

But this brings some restrictions on the interface. Read out (Generic Modifier) (C# Reference) for more info.

In a generic interface, a type parameter can be declared covariant if it satisfies the following conditions:

  1. The type parameter is used only as a return type of interface methods and not used as a type of method arguments.
  2. The type parameter is not used as a generic constraint for the interface methods.

Upvotes: 3

phil soady
phil soady

Reputation: 11328

Consider using Inversion of Control approach with a container. The various implementations register themselves in the container. The resolver enquires please an instance of (x). See Unity as 1 of many IOC Container tools.

Upvotes: 0

max
max

Reputation: 34407

Make IJobDao interface covariant:

public interface IJobDao<out T> where T : IJob
{
    T findById(long jobId);
}

Update:

You cannot have interface methods both returning and accepting generic values and make it covariant at the same time.

Possible solutions:

  • create a non-generic version of IJobDao<T> - IJobDao (of course, you'll have to implement both interfaces in classes, implementing IJobDao<T>)
  • split IJobDao<T> into 2 interfaces (one covariant and one contravariant)
  • consider a solution with only non-generic interface IJobDao (you are not getting any type-safety here anyway, which is the main purpose of generics)

Some ideas on implementing first scenario:

public interface IJobDao
{
    IJob findById(long jobId);

    long insert(IJob job);
}

public interface IJobDao<T> : IJobDao
    where T : IJob
{
    new T findById(long jobId);

    new long insert(T job);
}

public abstract class JobDaoBase<T> : IJobDao<T>, IJobDao
    where T : IJob
{
    public abstract T findById(long jobId);

    public abstract long insert(T job);

    IJob IJobDao.findById(long jobId)
    {
        return findById(jobId);
    }

    long IJobDao.insert(IJob job)
    {
        return insert((T)job);
    }
}

public class FullTimeJobDaoImpl : JobDaoBase<FullTimeJob>
{
    public override FullTimeJob findById(long jobId)
    {
        // implementation
    }

    public override long insert(FullTimeJob job)
    {
        // implementation
    }
}

// we are still unable to return generic interface, but we don't need to.
public static IJobDao createJobDao(/* my params */)
{
    object jobDao = Activator.CreateInstance(typeof(FullTimeJobDaoImpl));
    return jobDao as IJobDao;
}

Upvotes: 4

Related Questions