【问题标题】:Injecting Generic type parameters with AutoFac使用 AutoFac 注入通用类型参数
【发布时间】:2011-09-29 21:05:07
【问题描述】:

我想我真的很困惑我可以用 AutoFac 做什么,有人可以帮我走上正轨吗?

我有一个基本类型

class PersonBase{ 
    public string SaySomething(){
       return "I am base";
    }
}

我派生出两个具体的类

class FakePerson : PersonBase{
    public override string SaySomething(){
       return "I'm so Fake";
    }
}

class RealPerson : PersonBase{
    public override string SaySomething(){
       return "I am For Real";
    }
}

创建一个泛型类PersonH​​andler来处理不同类型的人,并希望PersonH​​andler在适当的时候实例化这个人,所以我不想注入Person的实例,只需要派生类型

class PersonHandler<T>
    where T : PersonBase, new() {

    T _Person;    

    public DoWork(){
        _Person = new T();
        _Person.SaySomething();
    }
}

现在我尝试使用处理程序,在注册了接下来详述的类型后,结果各不相同。

var ph = contrainer.Resolve<PersonHandler<PersonBase>>();
ph.DoWork();

我尝试注册类型如下

1. vBuilder.RegisterType<PersonHandler<FakePerson>>().As<PersonHandler<PersonBase>>();

这给了我一个错误,指出 PersonHandler&lt;FakePerson&gt; 不能分配给 PersonHandler&lt;PersonBase&gt;(或者相反,我不记得哪个)

2. vBuilder.RegisterGeneric<typeof(PersonHandler<>)>
   vBuilder.RegisterType<FakePerson>().As<PersonBase>();

这不会将PersonBase 解析为FakePerson,而只是给出PersonHandler&lt;PersonBase&gt;,所以它会导致“我是基地”

3. vBuilder.RegisterGeneric(typeof(PersonHandler<FakePerson>)).As(typeof(PersonHandler<PersonBase>));

这给出了一个错误,指出PersonHandler&lt;FakePerson&gt; 不是开放类型

所以现在我整天都在追逐我的故事,坦率地说,它变得乏味了,

请帮忙

【问题讨论】:

    标签: generics dependency-injection autofac


    【解决方案1】:

    (几乎)正确的解决方案是您已经尝试过的解决方案:

    builder.RegisterType<PersonHandler<FakePerson>>()
        .As<PersonHandler<PersonBase>>();
    

    Autofac 给你一个错误的原因是 C# 中的泛型类类型不能这样工作。

    也就是说,你不能写:

    PersonHandler<PersonBase> ph = new PersonHandler<FakePerson>();
    

    (在您的 IDE 中尝试一下 - 编译器会拒绝它。)

    原因是逆变,C#4 中添加的必需特性,只支持接口类型。

    长话短说,如果您创建 IPersonHandler&lt;T&gt; 并将其用作服务,那么上述代码将按预期工作:

    interface IPersonHandler<in T>
        where T : PersonBase, new() {
    
        void DoWork();
    }
    

    注意接口声明中的in 参数。

    使PersonHandler&lt;T&gt;实现IPersonHandler&lt;T&gt;

    class PersonHandler<T> : IPersonHandler<T>
        where T : PersonBase, new() {
    

    然后像这样注册:

    builder.RegisterType<PersonHandler<FakePerson>>()
        .As<IPersonHandler<PersonBase>>();
    

    然后您可以使用以下方法获取处理程序:

    IPersonHandler<PersonBase> handler =
        container.Resolve<IPersonHandler<PersonBase>>();
    

    返回的handler 对象将是PersonHandler&lt;FakePerson&gt; 类型。

    【讨论】:

      【解决方案2】:

      当您解析使用开放泛型类型(例如PersonHandler&lt;&gt;)注册的服务时,您需要指定要处理的确切封闭类型,Autofac 会为您生成一个。

      在这种情况下,您调用了container.Resolve&lt;PersonHandler&lt;PersonBase&gt;&gt;(),因此返回了PersonHandler&lt;PersonBase&gt;。现在,如果您查看 PersonHandler 类,并将 PersonBase 替换为 T,则生成的 DoWork 方法如下所示:

      public DoWork(){
          _Person = new PersonBase();
          _Person.SaySomething();
      }
      

      所以SaySomething() 调用使用了基类上的方法。

      我不确定您到底要做什么,但您似乎想决定在注册期间使用哪个具体的PersonBase 实现。如果是这种情况,您可以尝试使用 Autofac 的 Delegate Factory 功能:

      class PersonHandler
      {
          private readonly Func<PersonBase> createPerson;
      
          public PersonHandler(Func<PersonBase> createPerson)
          {
              this.createPerson = createPerson;
          }
      
          public void DoWork()
          {
              PersonBase pb = this.createPerson();
              Console.WriteLine(pb.SaySomething());
          }
      }
      

      然后您可以按如下方式注册您的课程:

      ContainerBuilder builder = new ContainerBuilder();
      builder.RegisterType<RealPerson>().As<PersonBase>();
      builder.RegisterType<PersonHandler>();
      var context = builder.Build();
      var handler = context.Resolve<PersonHandler>();
      handler.DoWork();
      

      通过更改此代码的第二行,您可以注册您实际想要在程序中使用的任何类型的PersonBase

      【讨论】:

      • 在我看来,Ricardo 试图让他的代码可测试,但我相信他的设计方向是错误的。我必须说我不喜欢直接使用委托作为委托,因为我认为这会使您的设计不够明确。然而,一些著名的 SO 用户确实喜欢使用它们。
      • 我不确定您所说的“将委托直接用作委托……使您的设计不够明确”是什么意思。 Autofac 的委托工厂特性是将 IoC 容器依赖项排除在代码库之外的好方法;但我同意一开始它可能有点不透明。您的解决方案同样有效 - 除了我认为行 vBuilder.RegisterType().As>() 应该阅读 vBuilder.RegisterType>().As ();
      • 那里有一点错别字。我的意思是:“直接使用工厂作为代表”。您对我的代码中的错误是正确的。谢谢。
      猜你喜欢
      • 2013-05-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-12-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多