如何在 Simple Injector 中组合开放通用和部分封闭通用注册

How to combine open generic and partially closed generic registrations in Simple Injector

我有如下界面

public interface IMapper<in TSource, out TDestination>
{
    TDestination Map(TSource source);
}

使用默认(后备)实现:

public class DefaultMapper<TSource, TDestination> : IMapper<TSource, TDestination>
{
   ...
}

并使用 Simple Injector 注册如下:

container.Register(typeof(IMapper<,>), MapperAssemblies);
container.RegisterConditional(typeof(IMapper<,>), typeof(DefaultMapper<,>),
    Lifestyle.Singleton,
    c => !c.Handled);

这允许我为特定情况编写特定的映射器,并且当缺少显式注册(在 MapperAssemblies 中)时,它会导致从容器中 return 编辑 DefaultMapper 实例。太棒了!

但是,在映射集合或其他开放通用 类 中存在很多重叠。我想避免为每个映射编写单独的实现,例如,从一个集合到另一个集合。我怎样才能 setup/adjust 我的代码并注册它,这样 Simple Injector 就会 return a:

IMapper<List<TSource>, List<TDestination>> 

IMapper<Source<T>, Destination<T>>

请求时。我尝试应用部分关闭的注册(根据文档),这适用于某些场景,但我丢失了通用类型,这使得映射变得更加困难。

使用简单注入器,这将是定义一个通用映射器实现的问题,该实现允许从源列表映射到目标列表,如下所示:

public class ListMapper<TSource, TDestination>
    : IMapper<List<TSource>, List<TDestination>>
{
    private readonly IMapper<TSource, TDestination> mapper;

    public ListMapper(IMapper<TSource, TDestination> mapper) => this.mapper = mapper;

    public List<TDestination> Map(List<TSource> source) =>
        source.Select(this.mapper.Map).ToList();
}

您可以按如下方式注册此映射器:

container.Register(typeof(IMapper<,>), MapperAssemblies);
container.Register(typeof(IMapper<,>), typeof(ListMapper<,>));

// Register last
container.RegisterConditional(typeof(IMapper<,>), typeof(DefaultMapper<,>),
    Lifestyle.Singleton,
    c => !c.Handled);

请注意 ListMapper<,> 是如何实现 IMapper<List<TSource>, List<TDestination>> 的?这与泛型类型约束具有相同的效果,允许简单注入器有条件地应用映射器。

如果您真的想变得更有趣,并且希望能够将任意集合类型映射到任意其他任意集合类型,您可以定义以下通用映射器:

public class EnumerableMapper<TIn, TInCollection, TOut, TOutCollection>
    : IMapper<TInCollection, TOutCollection>
    where TInCollection : IEnumerable<TIn>
    where TOutCollection : IEnumerable<TOut>, new()
{
    private readonly IMapper<TIn, TOut> mapper;

    public EnumerableMapper(IMapper<TIn, TOut> mapper) => this.mapper = mapper;

    public TOutCollection Map(TInCollection source) => ...;
}

即使这个 class 在 IMapper 抽象之上包含两个额外的泛型类型,Simple Injector 仍然能够确定所有类型应该是什么。您可以按如下方式注册此类型:

container.Register(typeof(IMapper<,>), typeof(EnumerableMapper<,,,>));

唯一困难的部分是正确实现其 Map 方法,这可能令人望而生畏,因为 return 类型可以是 任何实现 的东西 IEnumerable<T>,而您应该仍然能够创建它。