Search code examples
genericsreflectioninterfacedependenciesopen-generics

C#: Construct Type Constrained Half-Closed Generic Implementations of Open Generic Interface via Open Generic Method/Class


I am trying to construct instances of an open generic repository interface whereby the implementations impose more strict type constraints than the interface. Each implementation of the repository interface needs a specific implementation of the generic type to handle certain methods/operations based on the passed type's properties (not shown for the sake of brevity).

Here is a comprehensive example of the scenario:

public interface IRepository<T> where T : class
{
    //...
}

public class BaseRepository<T> : IRepository<T> where T : DbModel
{
    //...
}

public class SqlServerDbRepository<T> : BaseRepository<T> where T : SqlServerDbModel
{
    //...
}

public abstract class DbModel
{
    //...
}

// is further derived by other models
public class SqlServerDbModel : DbModel
{
    //...
}

public class User : SqlServerDbModel
{
}

// CLIENT CODE

public static IRepository<T> BuildRepository<T>()
    where T : class
{
    if (typeof(T) == typeof(SqlServerDbModel)) // "is" keyword will not work here (according to IDE, have not checked)
    {
        return new SqlServerDbRepository<T>(); // How can T be converted or accepted as an input of type "SqlServerDbModel" (the check already confirms it, so we know it should work)
    }
    else if (typeof(T) == typeof(DbModel))
    {
        return new BaseRepository<T>(); // How can T be converted or accepted as an input of type "DbModel" (the check already confirms it, so we know it should work)
    }
    //... else throw error or just return default...
}

// USAGE
public static void TestBuildRepository()
{
    var userRepository = BuildRepository<User>();
}

I tried initially running the scenario through an IOC container (Castle Windsor in case anybody is wondering) figuring it would figure out the type constraints automatically, however, this was not possible (or at least not with the way it handles open generics and dependency injection). I figured I could use a custom factory to build the interface implementations.

The problem is in the lines matching the pattern return new XYZRepository<T>(); whereby I am not sure how to get the c# compiler to take the generic type "T" passed to it, knowing it will fully satisfy the type constraint. I am sure this could be done through reflection, but I only found information on how to build methods and properties, not generic classes. How could this be achieved?

I cannot make any changes to the interfaces, repository implementations, or models...just in case anyone was going to make that suggestion.


Solution

  • It helps to write the problem out, and as it turns out, it was easier than I had originally anticipated. @CRAGIN's answer gave me the final missing piece (as far as...oh, yeah, we can cast to interfaces in C#).

    In case anybody from the future stumbles along...

    public static IRepository<T> BuildRepository<T>(params object[] constructor_arguments)
        where T : class
    {
        if (typeof(T) == typeof(SqlServerDbModel))
        {
            return (IRepository<T>)Activator.CreateInstance(typeof(SqlServerDbRepository<>).MakeGenericType(typeof(T)), constructor_arguments);
        }
        else if (typeof(T) == typeof(DbModel))
        {
            return (IRepository<T>)Activator.CreateInstance(typeof(BaseRepository<>).MakeGenericType(typeof(T)), constructor_arguments);
        }
        //... else throw error or just return default...
    }
    

    I needed to use the Activator.CreateInstance API to make the object and then just cast it back to the correct type. I wish there was a way to do this in Castle Windsor "natively" without resorting to a custom factory / reflection.