【问题标题】:Autofac: resolve an open generic with conditions on its type parametersAutofac:解析具有类型参数条件的开放泛型
【发布时间】:2015-09-10 08:56:40
【问题描述】:

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

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

和一个包装器接口,用于根据其输入参数类型动态选择适当的IMapper&lt;TSource, TTarget&gt;

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

我希望 IDynamicMapper 的实现在运行时使用 Autofac 找到适当的 IMapper&lt;TSource, TTarget&gt; 组件,该组件的 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&lt;&gt;).MakeGenericType(mapperType)) 时,组件未解析,尽管它存在于容器的注册中,映射到服务IMapper&lt;TSource, TTarget&gt;。第一次调用引发异常,第二次调用返回空枚举。

【问题讨论】:

标签: c# generics runtime autofac


【解决方案1】:

这应该可以解决问题:

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

【讨论】:

  • 刚试过这个:出现了 3 个问题。第一次我需要在我的IMapper&lt;,&gt; 实现类中切换到隐式接口实现(动态似乎不适用于显式接口实现),然后我需要用return mapper.GetTarget((dynamic)source); 替换最后一次调用,最后但并非最不重要的是,这个如果TTargetT 的子类而不是T 本身,则解决方案不起作用!在这种情况下,Resolve() 调用会引发异常。
  • @MaximeRossini:大约 1。是的,这是我过去偶然发现的一个奇怪的动态限制。我所做的是将映射器类型包装在一个类似装饰器的类型中,该类型既隐式实现又是公共类型(动态将在内部类型上失败,即使接口是公共的)。您甚至可以让 Autofac 为您解析 MapperWrapper&lt;,&gt;
  • @MaximeRossini:大约 3。这不起作用,因为 IMapper&lt;,&gt; 不是变体。您必须将其定义为IMapper&lt;TSource, out TTarget&gt;。在这种情况下,您应该解析一组映射器,并且必须决定在解析多个实例时要做什么;选择哪一个?
  • 大约 3。这正是我想要做的(拥有一组已解决的组件并选择第一个)。我只是试图使 TTarget 类型参数协变(out 修饰符),但它并不能阻止 ComponentNotRegisteredException 在调用 Resolve() 时...
  • Simple Injector 中的组件扫描可以通过使用Container.GetTypesToRegister 或关注this guide 来完成。我不完全确定可启动组件的功能是什么,但我认为您可以使用Packages 来实现它,尽管手动编码可能很简单。
【解决方案2】:

Autofac 不支持协变泛型类型 (ISomething&lt;out T&gt;)。在这种情况下,另一个 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);
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-06-10
    • 2014-11-25
    • 1970-01-01
    • 1970-01-01
    • 2010-12-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多