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.

推荐.NET配套的通用数据层ORM框架:CYQ.Data 通用数据层框架
新浪微博粉丝精灵,刷粉丝、刷评论、刷转发、企业商家微博营销必备工具"