Autofac:解析带有类型参数条件的开放泛型

本文关键字:条件 泛型 类型参数 Autofac | 更新日期: 2023-09-27 18:12:09

在使用Autofac作为其IoC容器的应用程序中,我有一个带有两个类型参数的泛型接口:

public interface IMapper<TSource, TTarget>
{
    TTarget GetTarget(TSource source);
}

和包装器接口,根据其输入参数类型动态选择适当的IMapper<TSource, TTarget>:

public interface IDynamicMapper
{
    T GetTarget<T>(object source);
}

我希望我的IDynamicMapper的实现找到在运行时适当的IMapper<TSource, TTarget>组件,使用Autofac,其中TSource等于source.GetType()TTargetT(或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>。第一次调用抛出一个异常,第二次调用返回一个空的可枚举对象。

Autofac:解析带有类型参数条件的开放泛型

这应该能奏效:

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