CesarGon
CesarGon

Reputation: 15345

Casting to generic type in C#

We have an abstract generic class in C#, very much like this:

public abstract class Repository<T>
    where T: Entity
{
    public abstract void Create(T t);
    public abstract T Retrieve(Id id);
    //etc.
}

We have a few derived classes, such as:

public class EventRepository
    : Repository<Event>
{
    //etc.
}

We are implementing a unit of work pattern that keeps a dictionary to map entity types to repository types, so that when an entity needs to be created or changed, it knows what repository to instantiate:

private Dictionary<Type, Type> m_dicMapper;

This dictionary is initialised and loaded with all the mappings, like this:

m_dicMapper.Add(typeof(Event), typeof(EventRepository));
//and so on for a few other repository classes.

Then, when an entity e needs to be created, for example:

//retrieve the repository type for the correct entity type.
Type tyRepo = m_dicMapper[e.GetType()];
//instantiate a repository of that type.
repo = Activator.CreateInstance(tyRepo);
//and now create the entity in persistence.
repo.Create(e);

The problem is, what type is repo in the code above? I would like to declare it as of generic Repository<T> type, but apparently C# won't let me do it. None of the following lines compile:

Repository repo;
Repository<T> repo;
Repository<e.GetType()> repo;

I can declare it as var, but then I don't get access to the Create and other methods that Repository<T> implements. I was hoping to be able to use the generic class to, well, use repositories generically! But I guess I am doing something wrong.

So my question is, what workarounds of coding and/or design could I use to solve this problem? Thank you.

Upvotes: 2

Views: 959

Answers (3)

CesarGon
CesarGon

Reputation: 15345

After much trying, I have found a solution to this. Basically, the problem was that I have a collection of things (the various repositories) that I want to treat similarly, from an abstract point of view, but they do not inherit from a common class; they are only particular reifications of a generic class, but that does not help much. Unless...

We use a repository registry class, as suggested by @Richard, having a method like this:

public Repository<T> GetRepository<T>(T t)  
    where T : Entity  
{  
    Type tyRepo = m_dic[t.GetType()];  
    Repository<T> repo = (Repository<T>)Activator.CreateInstance(tyRepo);  

    return repo;  
}

Note that the t argument is needed because the dictionary must be searched by t.GetType() rather than typeof(T), in order to consider the run-time (rather than compile-time) type of the entity. If we don't do this, all entities passed are treated as being of the root type Entity.

Similarly, the usage of the registry needs to take this into account. When invoking the method, dynamic must be used to allow the run-time to determine the type of the entity as needed, rather than fixing it at compile-time. The following code fails:

Event ev = new Event();
Entity e = ev;
var repo = registry.GetRepository(e);

because e, despite being an Event, is resolved at compile-time as an Entity as far as generic resolution is concerned. You need something like this:

Event ev = new Event();
dynamic e = ev;
var repo = registry.GetRepository(e);

And this solves my problem.

Thanks to @Richard for his suggestion to create a separate repository registry, and to everyone else who have provided feedback.

Upvotes: 0

Richard
Richard

Reputation: 8290

I would personally recommend encapsulating your access to the dictionary with a separate singleton class that then wraps your dictionary getters and setters, something like a RepositoryStore class.

I'd recommend changing Dictionary<Type, Type> to Dictionary<Type, object>, and then handle the casting within the RepositoryStore; something like so?

Update (using Types and Lazy<T>)

If you're using .NET 4, you could take full advantage of the Lazy class, and change the dictionary type to be IDictionary<Type, Lazy<object>>. I've amended my original answer to reflect how this might work:

class RepositoryStore
{
    private IDictionary<Type, Lazy<object>> Repositories { get; set; }

    public RepositoryStore()
    {
        this.Repositories = new Dictionary<Type, Lazy<object>>();
    }

    public RepositoryStore Add<T, TRepo>() where TRepo : Repository<T>
    {
        this.Repositories[typeof(T)] = new Lazy<object>(() => Activator.CreateInstance(typeof(TRepo)));
        return this;
    }

    public Repository<T> GetRepository<T>()
    {
        if (this.Repositories.ContainsKey(typeof(T)))
        {
            return this.Repositories[typeof(T)].Value as Repository<T>;
        }

        throw new KeyNotFoundException("Unable to find repository for type: " + typeof(T).Name);
    }
}

The usage is then pretty simple...

var repositoryStore = new RepositoryStore()
// ... set up the repository store, in the singleton?

Repository<MyObject> myObjectRepository = repositoryStore.GetRepository<MyObject>();
myObjectRepository.Create(new MyObject());

Upvotes: 2

svick
svick

Reputation: 245046

If you know the type you want at compile time, then you can use generics. For example, you could do something like:

m_dictMapper.Add<Event, EventRepository>();

…

var repo = m_dictMapper.Get<Event>(); // repo is statically typed as Repository<Event>
var e = repo.Create(); // e is Event

This requires some casting inside the implementation of dict mapper, but otherwise will work fine.

If you know the type only at runtime (e.g. you have it as a Type object), then your options are quite limited. I think the best option would be create a non-generic base type for Repository, which works with objects:

var repo = m_dictMapper.Get(typeof(Event)); // repo is statically typed as Repository
var e = repo.Create(); // e is object

Also, I find your Create() method very confusing. A method with that name should return the created instance, not do something to an existing instance.

Upvotes: 1

Related Questions