如何将具有开放通用类型的集合注入以通过Autofac服务

时间:2019-04-09 13:30:43

标签: c# generics dependency-injection autofac composite

我试图尽我所能解释我公认的复杂问题。让我知道是否有任何需要补充的内容。

简要背景

我有一个DbWrapperCollection用于存储DbWrapper<TInput. TOutput>(由于TInputTOutput会有所不同,因此该集合实际上只是非通用“容器”的列表)包含作为对象的通用对象以及作为System.Types的输入和输出–参见下面的实现

另一方面,我有可变数量的服务,所有服务都带有自己的IDbWrapperCollection,我想在启动时通过autofac注入。

基本上我想做的是:

builder.RegisterType<SaveApplDbWrapper>().As<DbWrapper<AppleDto, SavedAppleDbEntity>>()
    .Named<string>("fruitService");
builder.RegisterType<SaveOrangeDbWrapper>().As<IUcHandler<OrangeDto, OrangeDbEntity>>()
    .Named<string>("fruitService");
builder.RegisterType<SaveMelon>().As<IUcHandler<MelonDto, MelonDbEntity>>()
    .Named<string>("appleService");

builder.Register(c => new FruitService(c.ResolveNamed("appleService")))
    .As<IDbMapperService>();

我的问题

从上面可以看到,在调用ResolveNamed()时,我特别省略了预期的类型参数。这是因为我是autofac(在某种程度上是泛型)的新手,我特别不知道是否有任何策略可以插入开放的泛型DbWrappers列表并推迟关闭我的泛型类型。

我将尝试在下面解释为解决此问题而研究的策略,以及到目前为止的实现方法

我自己的研究

我的看法是,我可以为包装器创建一个非泛型基类,然后将它们保存为该基类,委派将原始泛型类型解析为该基类的责任,或者放弃我的包装器集合而转而支持我的服务构造函数上的特定参数(很无聊-与我的复合启发式实现不兼容)。

随着复合模式的普及,我想我不是第一个使用类似复合模式的解决方案并希望使用DI和IoC的“通用叶子”解决方案的人。

我打算像这样使用我的fruitService:

myFruitService.GetDbMapper<MyFruitDto, DbEntityForThatSaidFruit(myOrange);

该服务在其DbMapperCollection中进行查找,找到具有所提供类型参数的映射器,并调用其Save()的实现;

到目前为止的实施情况

对于那些好奇的人,这里是我的实现:

DbWrappers:

class SaveApplebWrapper : DbWrapper<TInput, TOutput>
// and plenty more wrapppers for any fruit imaginable

服务:

public abstract class DbMapperService : IDbMapperService
{
    public IWrapperCollection Wrappers { get; set; }

    protected BestandService(IWrapperCollection wrappers)
    {
        Wrappers = wrappers;
    }

    public DbWrapper<TInput, TResult> GetWrapper<TInput, TResult>()
    {
        return Wrappers.GetWrapper<TInput, TResult>();
    }
}

我的WrapperCollection帮助器类:

public struct WrapperKey
{
    public static WrapperKey NewWrapperKey <TInput, TResult>()
    {
        return new WrapperKey { InputType = typeof(TInput), ResultType = typeof(TResult) };
    }

    public Type InputType { get; set; }
    public Type ResultType { get; set; }
}

public struct WrapperContainer
{
    public WrapperContainer(object wrapper) : this()
    {
        Wrapper= wrapper;
    }

    public object Wrapper{ get; set; }

    public DbWrapper<TInput, TResult> GetWrapper<TInput, TResult>()
    {
        return Wrapper as DbWrapper<TInput, TResult>;
    }
}

还有我的WrapperCollection:

public class UcWrapperCollection : Dictionary<WrapperKey, WrapperContainer>,
    IDbWrapperCollection
{
    public void AddWrapper<TInput, TResult>(UcHandler<TInput, TResult> handler)
    {
        Add(WrapperKey.NewWrapperKey<TInput, TResult>(), new WrapperContainer(handler));
    }

    public DbWrapper<TInput, TResult> GetWrapper<TInput, TResult>()
    {
        var key = WrapperKey.NewWrapperKey<TInput, TResult>();
        return this[key].GetWrapper<TInput, TResult>();
    }
}

我没有运气看过的问题

我看过的一些问题,似乎都与我的情况无关(尽管我的问题可以由通用委托人解决,但我认为这不是解决我问题的最佳方法。

  • 使用Autofac注入通用类型参数
  • Autofac。如何在构造函数中注入开放的泛型委托
  • 如何使用Autofac注入泛型类型的工厂
  • 带有嵌套开放泛型的Autofac

1 个答案:

答案 0 :(得分:2)

我认为您无法做到自己想要的事情。抱歉,可能不是您想要的答案。我将向您展示原因,也许还有一些变通办法,但要有一个封闭的泛型的任意集合,这些集合直到解决才真正被关闭。

让我们忽略DI一秒钟,只考虑FruitService,我在问题中没有看到它,但是我们在这里的用法中看到了它:

builder.Register(c => new FruitService(c.ResolveNamed("appleService")))
    .As<IDbMapperService>();

请注意,我们可以看到FruitService实现了IDbMapperService,因为它已注册为该接口。

此外,我们可以看到FruitService看起来应该进行某种形式的收集,因为在注册示例中有两个名称相同的事物。

builder.RegisterType<SaveApplDbWrapper>().As<DbWrapper<AppleDto, SavedAppleDbEntity>>()
    .Named<string>("fruitService");
builder.RegisterType<SaveOrangeDbWrapper>().As<IUcHandler<OrangeDto, OrangeDbEntity>>()
    .Named<string>("fruitService");

我注意到它们都实现了 different 泛型类型。我必须根据其余问题假设它们没有通用的基类。

要使其更具体并超越Autofac部分,我认为它与更大的问题并不真正相关,让我们这样考虑:

var wrapper = new[] { CreateWrapper("appleService"), CreateHandler("appleService") };
var service = new FruitService(wrapper);

我们假设CreateWrapperCreateHandler都采用字符串,并且神奇地创建了适当的包装器/处理程序类型。没关系,怎么回事。

这里有两件事需要紧密联系:

  • FruitService构造函数中参数的类型是什么?
  • 您期望CreateWrapper("appleService")CreateHandler("appleService")返回什么?

我在这里基本上只有两个选择。

选项1:使用object

如果没有通用的基类,则所有内容都必须为object

public class FruitService : IDBMapperService
{
  private readonly IEnumerable<object> _wrappers;
  public FruitService(IEnumerable<object>wrapper)
  {
    this._wrapper = wrapper;
  }

  public object GetWrapper<TInput, TResult>()
  {
    object foundWrapper = null;
    // Search through the collection using a lot of reflection
    // to find the right wrapper, then
    return foundWrapper;
  }
}

尚不清楚是否可以将DbWrapper<TInput, TResult>强制转换为IUcHandler<TInput, TResult>,因此您甚至不能依靠它。没有共同点。

但是,假设有一个通用的基类。

选项2:使用通用基类

似乎已经有了DbWrapper<TInput, TResult>的概念。重要的是要注意,即使您定义了该泛型,一旦将其关闭,它们也是两种不同的类型。 DbWrapper<AppleDto, SavedAppleDbEntity>不可强制转换为DbWrapper<OrangeDto, SavedOrangeDbEntity>。泛型更像是“类模板”而不是基类。他们不是同一回事。

例如,您不能做

var collection = new DbWrapper<,>[]
{
  new DbWrapper<AppleDto, SavedAppleDbEntity>(),
  new DbWrapper<OrangeDto, SavedOrangeDbEntity>()
};

但是,如果您有通用的接口或基类,则可以...

var collection = new IDbWrapper[]
{
  new DbWrapper<AppleDto, SavedAppleDbEntity>(),
  new DbWrapper<OrangeDto, SavedOrangeDbEntity>()
};

但这意味着您可以切换到该页面,表面上可以使用通用界面。

public class FruitService : IDBMapperService
{
  private readonly IEnumerable<object> _wrappers;
  public FruitService(IEnumerable<object>wrapper)
  {
    this._wrapper = wrapper;
  }

  public IDbWrapper GetWrapper<TInput, TResult>()
  {
    IDbWrapper foundWrapper = null;
    // Search through the collection using a lot of reflection
    // to find the right wrapper, then
    return foundWrapper;

    // IDbWrapper could expose those `TInput` and `TResult`
    // types as properties on the interface, so the reflection
    // could be super simple and way more straight LINQ.
  }
}

您使用的代码只需使用IDbWrapper并调用非泛型方法即可完成工作。

将其恢复到Autofac ...

记住我提到的关键是弄清楚Create方法应该返回什么。还是FruitService构造函数期望什么?那。黑桃白的。

您可以将所有内容注册为键对象。

builder.RegisterType<SaveApplDbWrapper>()
       .Named<object>("fruitService");
builder.RegisterType<SaveOrangeDbWrapper>()
       .Named<object>("fruitService");
builder.RegisterType<SaveMelon>()
       .Named<object>("appleService");

builder
  .Register(c => new FruitService(c.ResolveNamed<IEnumerable<object>>("appleService")))
  .As<IDbMapperService>();

在我的示例中,Autofac 中的Resolve操作是创建方法。那里没有魔术。它只是创建对象。您仍然必须知道您想要提供什么类型。

或者您可以使用通用的基类。

builder.RegisterType<SaveApplDbWrapper>()
       .Named<IDbWrapper>("fruitService");
builder.RegisterType<SaveOrangeDbWrapper>()
       .Named<IDbWrapper>("fruitService");
builder.RegisterType<SaveMelon>()
       .Named<IDbWrapper>("appleService");

builder
  .Register(c => new FruitService(c.ResolveNamed<IEnumerable<IDbWrapper>>("appleService")))
  .As<IDbMapperService>();

如果您不介意将DI系统混入FruitService中,则可以执行以下操作:

public class FruitService
{
  private readonly ILifetimeScope _scope;
  public FruitService(ILifetimeScope scope)
  {
    this._scope = scope;
  }

  public DbWrapper<TInput, TResult> GetWrapper<TInput, TResult>()
  {
    var type = typeof(DbWrapper<TInput, TResult>);
    var wrapper = this._lifetimeScope.Resolve(type);
    return wrapper;
  }
}

您必须注册事物而不用命名它们,而AsDbWrapper,但是如果一切都基于此,那就行得通。

builder.RegisterType<SaveApplDbWrapper>()
       .As<DbWrapper<AppleDto, SavedAppleDbEntity>>();
// Must be DbWrapper, can also be other things...
builder.RegisterType<SaveOrangeDbWrapper>()
       .As<IUcHandler<OrangeDto, OrangeDbEntity>>()
       .As<DbWrapper<OrangeDto, OrangeDbEntity>>();
builder.RegisterType<SaveMelon>()
       .As<DbWrapper<MelonDto, MelonDbEntity>>()
       .As<IUcHandler<MelonDto, MelonDbEntity>>();

builder.RegisterType<FruitService>()
       .As<IDbMapperService>();

当您解析IDbMapperService时,FruitService构造函数将获得对其解析的生存期范围的引用。所有包装器都将在同一范围内解决。

人们通常不喜欢像这样将IoC引用混入其代码中,但这是我看到您不必再费心去思考反射或向上或向下投射的唯一方法。

祝你好运!