获取一个泛型接口的所有实现类型

4
我试图使用以下代码获取所有IEntityModelBuilder的实现,但它返回了一个空集合。
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...
    }
}

如果我使用

更改类型
var type = typeof(IEntityModelBuilder<User>);

类型获取代码运行良好,返回预期的UserModelBuilder。我如何使用泛型来做到这一点?

2个回答

13
尽管Slava的解决方案有效,但由于“Contains”的存在,它通常并不完全安全。有可能其他接口/类型包含您正在搜索的接口名称。在这种情况下,假设您有另一个名为“IEntityModelBuilderHelper”的接口。
此外,您可以轻松地将此代码通用化,使其更加强大。考虑以下两种方法:
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)));
}

而且,

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)));
}

前者将为您提供实现所提供的泛型类型定义(即typeof(MyGenericType<>)的所有类型,其中对泛型类型参数没有任何限制。后者将采用提供的类型约束来执行相同的操作。

考虑以下类型:

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<>));会返回3个类型:{ Foo, FooA, FooS },而var types = GetAllTypes(typeof(IFoo<>), typeof(IEntity));仅会返回两个类型:{ Foo, FooA }


7

您可以尝试使用示例进行工作。

声明:

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) { }
}

用法:

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();

运行得很好,但是是否可能使用Activator.GetInstance为返回的每种类型获取一个实例? - lucacelenza
越来越接近了:我该如何停止编译器对隐式转换(即_entitymodelBuilders = (IEntityModelBuilder<IEntity>)objects;)的抱怨? - lucacelenza
2
这个解决方案总的来说是脆弱的。理论上你可能会遇到名称冲突(IEntityModelBuilderHelper)并得到错误的结果。如果没有其他方法,我会这样做,但有更安全的解决方案可用。 - InBetween
@LucaCelenza,我更新了答案。你想要的(转换)只能在IEntityModelBuilder的协变(out 关键字)的情况下完成。 - Slava Utesinov
略微提高的做法是利用开放式泛型类型 typeof(Foo<>) 而不是封闭类型 typeof(Foo<string>) 来明确获取你想要的类型而避免冲突。在反射中,使用开放式泛型没有任何问题。 - Adam Houldsworth
我更喜欢 @InBetween 的答案,尽管我最终采用的是这个解决方案,但 Slava 指出的“协方差部分”才是指引我正确方向并帮助我克服强制类型转换问题的关键。 - lucacelenza

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接