Nate W
Nate W

Reputation: 320

Use Reflection to Find Interface<T> in Assembly Types

I'm not sure if it's the angle brackets that are causing me problems, but I'm having trouble coming up with an answer within the Stack Overflow answers. If there's one already out there, I'd welcome you to point me in the right direction.

To summarize, I have an Interface and I can't get reflection to find it. Let's set the stage:

Interfaces

interface ICrew
{
    string Name { get; set; }
}

interface IDataService<T>
{
    IEnumerable<T> GetItems();
}

Classes

class Crew : ICrew
{
    public string Name { get; set; }
}

class CrewRepository : IDataService<ICrew>
{
    DbContext _context { get; }

    public CrewRepository(DbContext context)
    {
        _context = context;
    }


    public IEnumerable<ICrew> GetItems()
    {
        return _context.Crews;
    }
}

class DbContext
{
    public DbContext()
    {
        Crews = new List<Crew>();
    }
        
    public List<Crew> Crews;
}

Code

class Program
{
    private static DbContext _context;

    static void Main(string[] args)
    {
        _context = new DbContext();

        //I want to find CrewRepository (which is type of IDataService<ICrew>)
        var dataService = Get(typeof(ICrew));
    }

    private static object Get(Type T)
    {
        var types = typeof(CrewRepository).Assembly.GetTypes();

        var test = types.Where(t => t.IsAssignableFrom(T)); //Finds ICrew

        var test2 = types.Where(t => t.GetInterfaces().Contains(T)); //Finds Crew

        var test3 = types.Where(t => t.GetInterfaces().Contains(typeof(IDataService<>))); //Finds nothing

        var test4 = types.Where(t => t.IsAssignableFrom(typeof(IDataService<>))); //Finds interface IDataService

        var test5 = types.Where(t => typeof(IDataService<>).IsAssignableFrom(t)); //Finds interface IDataService

        var instance = types
            .Where(t => t.IsAssignableFrom(T))
            .FirstOrDefault();

        if (instance != null)
        {
            return Activator.CreateInstance(instance, _context);
        }

        return default;
    }

}

I can't figure out how to query the Assembly Types correctly to get IDataService<T>. My Get method doesn't know what type it's going to be asked for until runtime, and generics inside of angle brackets are only able to be used if I know the type at compile time. Can anyone help me figure out what I should be doing here to get Get to return the correct class Repository that I'm hoping for by using reflection?

Upvotes: 1

Views: 752

Answers (2)

Neil Barnwell
Neil Barnwell

Reputation: 42125

Okay this has already been answered, but just as an addition to that answer (which I agree with entirely and having built this project out myself, I realise my code almost exactly matches @thehennyy's), there is this:

Personally, rather than asking if a particular interface "is one of the interfaces according to Type.GetInterfaces()", I just prefer to ask for types that implement a given interface. It's basically the same, but the semantics can be important.

So my code looks like this:

private static object Get2(Type t)
{
    // Create the closed generic type that we need
    var closedType = typeof(IDataService<>).MakeGenericType(t);

    var allTypes = typeof(CrewRepository).Assembly.GetTypes();

    var dataServiceType = allTypes.Single(x => closedType.IsAssignableFrom(x));

    return Activator.CreateInstance(dataServiceType, _context);
}

However if we're using generics, we can do better and avoid that closed/open generic type business entirely:

private static IDataService<T> Get3<T>()
{
    var allTypes = typeof(CrewRepository).Assembly.GetTypes();

    var dataServiceType = allTypes.Single(x => typeof(IDataService<T>).IsAssignableFrom(x));

    return (IDataService<T>)Activator.CreateInstance(dataServiceType, _context);
}

static void Main(string[] args)
{
    _context = new DbContext();

    //I want to find CrewRepository (which is type of IDataService<ICrew>)
    var dataService3 = Get3<ICrew>();
}

Upvotes: 0

thehennyy
thehennyy

Reputation: 4218

As mentioned in the comments, have to use the Type.MakeGenericType method to create the needed type IDataService<"T"> at runtime. Your Get method could look like this:

private static object Get(Type t)
{
    var types = typeof(CrewRepository).Assembly.GetTypes();
    var runtimeType = typeof(IDataService<>).MakeGenericType(t);
    var type = types.SingleOrDefault(x => x.GetInterfaces().Contains(runtimeType));

    if (type != null)
    {
        return Activator.CreateInstance(type, _context);
    }

    return null;
}

Demo: https://dotnetfiddle.net/vRJzr8

Upvotes: 3

Related Questions