Click here to Skip to main content
Click here to Skip to main content
Technical Blog

The Non-Generic Generic Pattern

, 21 Nov 2011 CPOL
Rate this:
Please Sign up or sign in to vote.
From time to time I've needed to use generic classes but haven't known what the generic arguments are until runtime - for example because I'm iterating over classes I've found through reflection or I've read from a database. Hopefully, the contrived example below will explain what I mean.

From time to time I've needed to use generic classes but haven't known what the generic arguments are until runtime - for example, because I'm iterating over classes I've found through Reflection or I've read from a database. Hopefully, the contrived example below will explain what I mean.

Imagine I have the three entity classes below, which all implement the IEntity interface and can be read with the Repository class.

public interface IEntity
{
    int Id { get; set; }
}

public class Customer : IEntity
{
    public int Id { get; set; }
}

public class Product : IEntity
{
    public int Id { get; set; }
}

public class Supplier : IEntity
{
    public int Id { get; set; }
}

public class Repository<TEntity> where TEntity : IEntity, new()
{
    public TEntity Get(int id)
    {
        return new TEntity()
        {
            Id = id
        };
    }
}

Let's now say I wanted to read an instance of each of these items, possibly to display to the user. I said this example was contrived! A more realistic scenario maybe a generic administrative tool that allows a user to select a type of entity to view and gives them the chance to enter the Id of the entity to view, but I wanted to keep things simple.

The following LINQ query will get me all of the different types of entity, but how can we use the generic Repository class to read them?

IEnumerable<Type> entityTypes =
        from t in Assembly.GetAssembly(typeof(IEntity)).GetTypes()
        where typeof(IEntity).IsAssignableFrom(t) && t.IsClass
        select t;

What we would like to do is something like the following, which is invalid C#

foreach (Type entityType in entityTypes)
{
        var repository = new Repository<entityType>()
        ...
}

My solution is to create a private inner class which implements a private inner interface.

private interface IInnerRepository
{
   IEntity Get(int id);
}

private class InnerRepository<TEntity> : IInnerRepository where TEntity : IEntity, new()
{
    private readonly Repository<TEntity> _repository;

    public InnerRepository()
    {
        _repository = new Repository<TEntity>();
    }

    public IEntity Get(int id)
    {
        return _repository.Get(id);
    }
}

We can then create a generic type passing in the type of entity we are dealing with and then create an instance of that generic class with the Activator class.

private static IEntity LoadEntity(Type type, int id)
{
    // Check that type is valid as a generic argument
    Type genericType = typeof(InnerRepository<>).MakeGenericType(type);
    IInnerRepository repository = (IInnerRepository)Activator.CreateInstance(genericType);

    return repository.Get(id);
}

This makes for what I believe is quite an elegant solution to a recurring problem with generics. The majority of the code is type safe and can be checked by the compiler. There is a small amount of reflection but it is again fairly self contained and could itself be wrapped up in a static utility or extension method. This solution is probably slightly overkill for the above example as ultimately you could just create an instance of Repository rather than InnerRepository, but if there were more generic calls or classes to use, hopefully you can see the benefits of encapsulating them in an inner class like this.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

S1mm0t

United Kingdom United Kingdom
No Biography provided

Comments and Discussions

 
GeneralMy vote of 4 PinmemberJasmine250128-Nov-11 13:52 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.141220.1 | Last Updated 21 Nov 2011
Article Copyright 2011 by S1mm0t
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid