lucacelenza
lucacelenza

Reputation: 1369

Get all implementations types of a generic interface

I'm trying to get all the implementations of IEntityModelBuilder with the following code, but instead it returns an empty collection.

public class EntityFrameworkDbContext : DbContext
{
    //constructor(s) and entities DbSets...

    private static IEnumerable<IEntityModelBuilder<IEntity>> _entitymodelBuilders;
    internal IEnumerable<IEntityModelBuilder<IEntity>> EntityModelBuilders
    {
        get
        {
            if (_entitymodelBuilders == null)
            {
                var type = typeof(IEntityModelBuilder<IEntity>);

                _entitymodelBuilders = Assembly.GetAssembly(type).GetTypes()
                    .Where(t => type.IsAssignableFrom(t) && t.IsClass)
                    .Select(t => (IEntityModelBuilder<IEntity>)Activator.CreateInstance(t, new object[0]));
            }

            return _entitymodelBuilders;
        }
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        foreach (var builder in EntityModelBuilders)
            builder.Build(modelBuilder);

        base.OnModelCreating(modelBuilder);
    }
}

internal interface IEntityModelBuilder<TEntity> where TEntity : IEntity
{
    void Build(DbModelBuilder modelBuilder);
}

//sample implementation
internal class UserModelBuilder : IEntityModelBuilder<User>
{
    public void Build(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<User>()
            .ToTable("users")
            .HasKey(e => e.Id);

        modelBuilder.Entity<User>()
            .Property(e => e.Id)
            .HasColumnName("id");

        modelBuilder.Entity<User>()
            .Property(e => e.Email)
            .HasColumnName("email");

        //and so on...
    }
}

If I change the type with

var type = typeof(IEntityModelBuilder<User>);

the types fetching code runs fine and returns the expected UserModelBuilder. How can I do this with generics?

Upvotes: 4

Views: 6492

Answers (2)

Slava Utesinov
Slava Utesinov

Reputation: 13498

You can try working example.

Declarations:

public interface IEntity { }
public class Entity1 : IEntity { }
public class Entity2 : IEntity { }

public interface IEntityModelBuilder<out T> where T : IEntity { }

public class BaseClass1 : IEntityModelBuilder<Entity1>
{        
    public BaseClass1(int a) { }
}
public class BaseClass2 : IEntityModelBuilder<Entity2>
{
    public BaseClass2(int a) { }
}

Usage:

List<IEntityModelBuilder<IEntity>> objects = Assembly.GetExecutingAssembly().GetTypes()
    .Where(x => x.GetInterfaces().Any(y => y.IsGenericType && && y.Name == "IEntityModelBuilder`1"))
    .Select(x => (IEntityModelBuilder<IEntity>)Activator.CreateInstance(x, new object[] { 0 })).ToList();

Upvotes: 7

InBetween
InBetween

Reputation: 32790

Although Slava's solution works, it isn't, in general, completely safe because of Contains. It is possible that some other interface/type could contain the name of the interface you are searching for. In this case, imagine you have another interface named IEntityModelBuilderHelper.

Also, with very little effort you can generalize this code to be much more poweful. Consider the following two methods:

public static IEnumerable<Type> GetAllTypes(Type genericType)
{
    if (!genericType.IsGenericTypeDefinition)
        throw new ArgumentException("Specified type must be a generic type definition.", nameof(genericType));

    return Assembly.GetExecutingAssembly()
                   .GetTypes()
                   .Where(t => t.GetInterfaces()
                                .Any(i => i.IsGenericType &&
                                     i.GetGenericTypeDefinition().Equals(genericType)));
}

And,

public static IEnumerable<Type> GetAllTypes(Type genericType, params Type[] genericParameterTypes)
{
    if (!genericType.IsGenericTypeDefinition)
        throw new ArgumentException("Specified type must be a generic type definition.", nameof(genericType));

    return Assembly.GetExecutingAssembly()
                   .GetTypes()
                   .Where(t => t.GetInterfaces()
                                .Any(i => i.IsGenericType &&
                                          i.GetGenericTypeDefinition().Equals(genericType) &&
                                          i.GetGenericArguments().Count() == genericParameterTypes.Length &&
                                          i.GetGenericArguments().Zip(genericParameterTypes, 
                                                                      (f, s) => s.IsAssignableFrom(f))
                                                                 .All(z => z)));
}

The former will give you all types that implement the supplied generic type definition, that is typeof(MyGenericType<>), with no constrain whatsoever on the generic type parameter. The latter will do the same thing but with the supplied type constraints.

Consider the following types:

public interface IFoo<T> { }
public interface IEntity { }
public class A : IEntity { }

public class Foo : IFoo<IEntity> { }
public class FooA : IFoo<A> { }
public class FooS : IFoo<string> { }

var types = GetAllTypes(typeof(IFoo<>)); will return 3 types: { Foo, FooA, FooS } while var types = GetAllTypes(typeof(IFoo<>), typeof(IEntity)); will return only two types: { Foo, FooA }.

Upvotes: 13

Related Questions