获取泛型服务类型的所有实现,包括开放泛型
本文关键字:泛型 实现 包括开 服务 类型 获取 | 更新日期: 2023-09-27 17:52:50
背景
我正在编写一些集成测试,测试特定接口IQueryMapping<TSource, TDest>
的实现。该接口的存在是为了从IQueryable<TSource>
映射到IQueryable<TDest>
。我想确保他们这样做使用实体框架编译查询时不引发异常。
任务
我很懒,不想每次创建新映射时都更新测试!我所要做的就是确保我的应用程序使用的每个映射都能通过测试。我可以引导我的容器,然后找到所有注册的实现,如下所示:
from r in Container.GetCurrentRegistrations()
let t = r.ServiceType
where t.IsGenericType && t.GetGenericTypeDefinition() == typeof (IQueryMapping<,>)
select r.GetInstance()
到目前为止,一切都很好!
问题
除了我的具体实现之外,我还有一个默认的开放通用映射器,它执行基本的自动属性映射(记住,我很懒,不想手动这样做!(
container.RegisterOpenGeneric(typeof(IQueryMapping<,>), typeof(DefaultAutoMapping<,>));
不幸的是,开放泛型似乎没有出现在Container.GetCurrentRegistrations()
调用中。来自文档:
返回具有当前注册的数组。此列表包含所有显式注册的类型和所有隐式注册的实例。隐式注册是已请求的所有具体未注册类型、已使用未注册类型解析(使用ResolveUnregisteredType事件(解析的所有类型以及已请求的未注册集合。请注意,由于这些隐式注册,此方法的结果可能会随着时间的推移而变化。
我真正希望Simple Injector告诉我所有注册组件中请求的IQueryMapping<,>
的每一次出现。
例如,如果我将IQueryMapping<Foo, Bar>
保留为开放的泛型DefaultAutoMapping<,>
,并且注册的组件具有构造函数签名
public MyComponent(IQueryMapping<Foo, Bar> mapping)
{
...
}
我希望容器告诉我这个专门的IQueryMapping<,>
,这样我就可以请求它的实例并在测试中运行它。
对RegisterOpenGeneric
的调用将在后台将委托挂接到ResolveUnregisteredType
事件上。这基本上意味着容器本身完全不知道注册,并且只有在请求注册抽象的封闭通用版本时才会添加注册;或者直接使用对CCD_ 11的调用,或者因为解析了依赖于该抽象的类型而间接地。
这里的技巧是在调用GetCurrentRegistrations()
之前先调用Verify()
。对Verify()
的调用将导致容器构建所有表达式树,编译所有委托,并创建容器已知的所有注册的所有实例。这将强制容器添加每个已找到的开放泛型抽象的封闭泛型版本的注册。
长话短说:先打电话给Verify()
。
据我所知,没有内置的方法可以做到这一点。然而,当我写我的问题(经常发生(时,我找到了一种实现我想做的事情的方法。这可能远非理想,但。。。
从Simple Injector Pipeline文档来看,这些信息在注册时似乎不容易获得——它只在解析时计算(在"Build ctor arguments"处(。
取1
我想到的一个想法是迭代每个注册的类型,并检查其构造函数中可能的参数:
from r in container.GetCurrentRegistrations()
from ctor in r.Registration.ImplementationType.GetConstructors()
from param in ctor.GetParameters()
let t = param.ParameterType
where t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IQueryMapping<,>)
select t;
然而,这只会降到注册类型的第一级——在我的项目中,有许多开放的泛型注册。
取2
幸运的是,SimpleInjector提供了一种基于服务类型检索InstanceProducer
的方法,这就是我们创建递归函数所需要的:
public static class ContainerExtensions
{
public static IEnumerable<InstanceProducer> GetInstanceProducers(this Container container)
{
return container.GetCurrentRegistrations()
.SelectMany(x => GetInstanceProducers(container, x));
}
private static IEnumerable<InstanceProducer> GetInstanceProducers(Container container, InstanceProducer instanceProducer)
{
yield return instanceProducer;
var producers = from ctor in instanceProducer.Registration
.ImplementationType.GetConstructors()
from param in ctor.GetParameters()
from producer in GetInstanceProducers(
container,
container.GetRegistration(param.ParameterType))
select producer;
foreach (var producer in producers)
yield return producer;
}
}
这会在所有注册的类型中重复出现,通过查找它们的构造函数来查找要搜索的其他类型。然而,这仍然不是完美的,因为我们不能保证特定的组件应该通过其构造函数(而不是工厂方法(来解决。
取3
CCD_ 17的一个有趣的方法是CCD_。此方法创建一个表达式,该表达式在执行时将创建给定的实例。但是,因为它是一个表达式,所以也可以使用ExpressionVisitor来遍历它。我们可以创建ExpressionVisitor的实现,它可以收集表达式中的所有类型:
public static class ContainerExtensions
{
public static IEnumerable<InstanceProducer> GetInstanceProducers(this Container container)
{
return container.GetCurrentRegistrations()
.SelectMany(GetExpressionTypes)
.Distinct()
.Select(container.GetRegistration);
}
private static IEnumerable<Type> GetExpressionTypes(InstanceProducer instanceProducer)
{
var expression = instanceProducer.BuildExpression();
var visitor = new TypeExpressionVisitor();
visitor.Visit(expression);
return visitor.Types;
}
private class TypeExpressionVisitor : ExpressionVisitor
{
private readonly List<Type> _types;
public IEnumerable<Type> Types
{
get { return _types; }
}
public TypeExpressionVisitor()
{
_types = new List<Type>();
}
protected override Expression VisitNew(NewExpression node)
{
_types.Add(node.Type);
return base.VisitNew(node);
}
protected override Expression VisitInvocation(InvocationExpression node)
{
_types.Add(node.Type);
return base.VisitInvocation(node);
}
}
}
终于!ExpressionVisitor收集的类型可以传递给container.GetRegistration(t)
。类型将是具体的类型,因此我们需要对Take 1中的LINQ语句进行一个小的更改,使用一种方法来测试服务类型是否可分配给IQueryMapping<,>
:的任何通用版本
public static IEnumerable<object[]> GetMappingObjects
{
get
{
return
from r in Container.GetInstanceProducers()
where IsAssignableToGenericType(r.ServiceType, typeof(IQueryMapping<,>))
select new[] {r.GetInstance()};
}
}
public static bool IsAssignableToGenericType(Type givenType, Type genericType)
{
while (true)
{
var interfaceTypes = givenType.GetInterfaces();
if (givenType.IsGenericType && givenType.GetGenericTypeDefinition() == genericType)
return true;
if (interfaceTypes.Any(it => it.IsGenericType && it.GetGenericTypeDefinition() == genericType))
return true;
var baseType = givenType.BaseType;
if (baseType == null)
return false;
givenType = baseType;
}
}
我想知道我做这件事是对的,还是无意中把自己挖进了一个洞,所以如果你在这方面很了解,请给我反馈!