Autofac:使用类型参数条件解析开放式泛型
Autofac: resolve an open generic with conditions on its type parameters
在使用 Autofac 作为其 IoC 容器的应用程序中,我有一个带有两个类型参数的通用接口:
public interface IMapper<TSource, TTarget>
{
TTarget GetTarget(TSource source);
}
和一个包装器接口,用于根据其输入参数类型动态 select 适当的 IMapper<TSource, TTarget>
:
public interface IDynamicMapper
{
T GetTarget<T>(object source);
}
我希望我的 IDynamicMapper 实现在运行时 找到合适的IMapper<TSource, TTarget>
组件,使用 Autofac,它有一个 TSource
等于 source.GetType()
和 TTarget
是 T
的派生类型(或 T
本身):
public class DynamicMapper : IDynamicMapper
{
private readonly ILifetimeScope _scope;
public DynamicMapper(ILifetimeScope scope)
{
this._scope = scope;
}
T IDynamicMapper.GetTarget<T>(object source)
{
Type sourceType = source.GetType();
Type targetBaseType = typeof(T);
//TODO: find an IMapper<TSource, TTarget> implementation where
// 1) Condition on TSource: typeof(TSource) == sourceType
// 2) Condition on TTarget: targetBaseType.IsAssignableFrom(typeof(TTarget))
// Many implementations can potentially match these criterias,
// choose the 1st one
// (this should not happen if the application is designed correctly)
if (mapper == null)
{
throw new ArgumentException(
"Could not find an IMapper<TSource, TTarget> implementation" +
" for the supplied parameters"
);
}
// call mapper.GetTarget(source) and return the result
// (mapper is probably dynamic, but its runtime type implements
// TTarget IMapper<TSource, TTarget>.GetTarget(TSource source))
}
}
我的所有组件都注册到 Autofac 容器作为它们在应用程序另一部分的服务接口(使用程序集扫描记录)。
更新 1
根据 Steven 的相关回答,我更新了我的界面以使用方差:
public interface IMapper<in TSource, out TTarget>
{
TTarget GetTarget(TSource source);
}
我的动态映射器的 GetTarget()
方法如下所示:
T IDynamicMapper.GetTarget<T>(object source)
{
Type sourceType = source.GetType();
Type targetBaseType = typeof(TTarget);
Type mapperType = typeof(IMapper<,>).MakeGenericType(sourceType, targetBaseType);
// This fails with ComponentNotRegisteredException
dynamic mapper = this._scope.Resolve(mapperType);
// This also fails (mapper is null):
// IEnumerable<object> mappers = (IEnumerable<object>)this._scope.Resolve(typeof(IEnumerable<>).MakeGenericType(mapperType));
// dynamic mapper = mappers.FirstOrDefault();
// Invoke method
return mapper.GetTarget((dynamic)source);
}
然而,当调用 Resolve(mapperType)
或 Resolve(typeof(IEnumerable<>).MakeGenericType(mapperType))
时,该组件未解析,尽管它存在于容器的注册中,映射到服务 IMapper<TSource, TTarget>
。第一次调用抛出异常,第二次调用 returns 一个空的可枚举对象。
这应该可以解决问题:
T IDynamicMapper.GetTarget<T>(object source) {
Type mapperType = typeof(IMapper<,>).MakeGenericType(source.GetType(), typeof(T));
// Will throw when no registration exists.
// Note the use of 'dynamic'.
dynamic mapper = scope.Resolve(mapperType);
return (T)mapper.GetTarget<T>((dynamic)source);
}
Autofac 不支持协变泛型类型 (ISomething<out T>
)。在这种情况下,另一个 IoC 容器(如 Simple Injector)可以解决这个问题,但为了让它与 Autofac 一起工作,我最终使用了另一个接口:
服务:
public interface IMapper<TSource, out TTarget> : IMapperLocator<TSource>
{
TTarget Extract(TSource source);
}
public interface IMapperLocator<TSource>
{
}
public interface IDynamicMapper
{
T Extract<T>(object source);
}
实施:
public class DynamicMapper : IDynamicMapper
{
private readonly ILifetimeScope _scope;
public DynamicMapper(ILifetimeScope scope)
{
this._scope = scope;
}
T IDynamicMapper.Extract<T>(object source)
{
// Get useful types
Type sourceType = source.GetType();
Type targetBaseType = typeof(TTarget);
Type mapperBaseType = typeof(IMapper<,>).MakeGenericType(sourceType, targetBaseType);
Type locatorType = typeof(IMapperLocator<>).MakeGenericType(sourceType);
Type enumType = typeof(IEnumerable<>).MakeGenericType(locatorType);
// Get all mapper implementations that take a TSource with the
// same type as the source object
var mappers = (IEnumerable<object>)this._scope.Resolve(enumType);
// Among all the mappers with the right TSource, select the one
// with TTarget assignable to T (throws if there is 0 or more than
// one mapper, as this would be an implementation error)
dynamic mapper = mappers.Single(x => mapperBaseType.IsAssignableFrom(x.GetType()));
// The method must implemented implicitly.
// A workaround would be to use a wrapper (IMapperWrapper<TSource, out TTarget>)
// that implements the method implicitly and invokes the mapper's method
// without using dynamic
return mapper.Extract((dynamic)source);
}
}
在使用 Autofac 作为其 IoC 容器的应用程序中,我有一个带有两个类型参数的通用接口:
public interface IMapper<TSource, TTarget>
{
TTarget GetTarget(TSource source);
}
和一个包装器接口,用于根据其输入参数类型动态 select 适当的 IMapper<TSource, TTarget>
:
public interface IDynamicMapper
{
T GetTarget<T>(object source);
}
我希望我的 IDynamicMapper 实现在运行时 找到合适的IMapper<TSource, TTarget>
组件,使用 Autofac,它有一个 TSource
等于 source.GetType()
和 TTarget
是 T
的派生类型(或 T
本身):
public class DynamicMapper : IDynamicMapper
{
private readonly ILifetimeScope _scope;
public DynamicMapper(ILifetimeScope scope)
{
this._scope = scope;
}
T IDynamicMapper.GetTarget<T>(object source)
{
Type sourceType = source.GetType();
Type targetBaseType = typeof(T);
//TODO: find an IMapper<TSource, TTarget> implementation where
// 1) Condition on TSource: typeof(TSource) == sourceType
// 2) Condition on TTarget: targetBaseType.IsAssignableFrom(typeof(TTarget))
// Many implementations can potentially match these criterias,
// choose the 1st one
// (this should not happen if the application is designed correctly)
if (mapper == null)
{
throw new ArgumentException(
"Could not find an IMapper<TSource, TTarget> implementation" +
" for the supplied parameters"
);
}
// call mapper.GetTarget(source) and return the result
// (mapper is probably dynamic, but its runtime type implements
// TTarget IMapper<TSource, TTarget>.GetTarget(TSource source))
}
}
我的所有组件都注册到 Autofac 容器作为它们在应用程序另一部分的服务接口(使用程序集扫描记录)。
更新 1
根据 Steven 的相关回答,我更新了我的界面以使用方差:
public interface IMapper<in TSource, out TTarget>
{
TTarget GetTarget(TSource source);
}
我的动态映射器的 GetTarget()
方法如下所示:
T IDynamicMapper.GetTarget<T>(object source)
{
Type sourceType = source.GetType();
Type targetBaseType = typeof(TTarget);
Type mapperType = typeof(IMapper<,>).MakeGenericType(sourceType, targetBaseType);
// This fails with ComponentNotRegisteredException
dynamic mapper = this._scope.Resolve(mapperType);
// This also fails (mapper is null):
// IEnumerable<object> mappers = (IEnumerable<object>)this._scope.Resolve(typeof(IEnumerable<>).MakeGenericType(mapperType));
// dynamic mapper = mappers.FirstOrDefault();
// Invoke method
return mapper.GetTarget((dynamic)source);
}
然而,当调用 Resolve(mapperType)
或 Resolve(typeof(IEnumerable<>).MakeGenericType(mapperType))
时,该组件未解析,尽管它存在于容器的注册中,映射到服务 IMapper<TSource, TTarget>
。第一次调用抛出异常,第二次调用 returns 一个空的可枚举对象。
这应该可以解决问题:
T IDynamicMapper.GetTarget<T>(object source) {
Type mapperType = typeof(IMapper<,>).MakeGenericType(source.GetType(), typeof(T));
// Will throw when no registration exists.
// Note the use of 'dynamic'.
dynamic mapper = scope.Resolve(mapperType);
return (T)mapper.GetTarget<T>((dynamic)source);
}
Autofac 不支持协变泛型类型 (ISomething<out T>
)。在这种情况下,另一个 IoC 容器(如 Simple Injector)可以解决这个问题,但为了让它与 Autofac 一起工作,我最终使用了另一个接口:
服务:
public interface IMapper<TSource, out TTarget> : IMapperLocator<TSource>
{
TTarget Extract(TSource source);
}
public interface IMapperLocator<TSource>
{
}
public interface IDynamicMapper
{
T Extract<T>(object source);
}
实施:
public class DynamicMapper : IDynamicMapper
{
private readonly ILifetimeScope _scope;
public DynamicMapper(ILifetimeScope scope)
{
this._scope = scope;
}
T IDynamicMapper.Extract<T>(object source)
{
// Get useful types
Type sourceType = source.GetType();
Type targetBaseType = typeof(TTarget);
Type mapperBaseType = typeof(IMapper<,>).MakeGenericType(sourceType, targetBaseType);
Type locatorType = typeof(IMapperLocator<>).MakeGenericType(sourceType);
Type enumType = typeof(IEnumerable<>).MakeGenericType(locatorType);
// Get all mapper implementations that take a TSource with the
// same type as the source object
var mappers = (IEnumerable<object>)this._scope.Resolve(enumType);
// Among all the mappers with the right TSource, select the one
// with TTarget assignable to T (throws if there is 0 or more than
// one mapper, as this would be an implementation error)
dynamic mapper = mappers.Single(x => mapperBaseType.IsAssignableFrom(x.GetType()));
// The method must implemented implicitly.
// A workaround would be to use a wrapper (IMapperWrapper<TSource, out TTarget>)
// that implements the method implicitly and invokes the mapper's method
// without using dynamic
return mapper.Extract((dynamic)source);
}
}