IoC,依赖注入和构造函数参数

时间:2015-10-07 13:03:21

标签: c# .net dependency-injection inversion-of-control

我希望能够根据Inversion of Control原则创建一个服务,因此我创建了一个接口和一个服务类。

public interface IMyService
{
    void DoSomeThing1();
    void DoSomeThing2();
    void DoSomeThing3();
    string GetSomething();

}

public class MyService : IMyService
{
    int _initialValue;
    //...

    public MyService(int initialValue)
    {
        _initialValue = initialValue;
    }

    public void DoSomeThing1()
    {
        //Do something with _initialValue
        //...
    }

    public void DoSomeThing2()
    {
        //Do something with _initialValue
        //...
    }

    public void DoSomeThing3()
    {
        //Do something with _initialValue
        //...
    }

    public string GetSomething()
    {
        //Get something with _initialValue
        //...
    }
}

以Unity为例,我可以设置我的IoC。

public static class MyServiceIoc
{
    public static readonly IUnityContainer Container;

    static ServiceIoc()
    {
        IUnityContainer container = new UnityContainer();
        container.RegisterType<IMyService, MyService>();
        Container = container;
    }
}

问题是构造函数参数。我可以使用像

这样的ParameterOverride
var service = MyServiceIoc.Container.Resolve<IMyService>(new ParameterOverrides
{                                                                                   
    {"initialValue", 42}
});

但我不想使用输入错误的参数。如果有人更改构造函数参数名称或添加一个参数怎么办?他不会在完成时被警告,也许除了最终用户之外没有人会发现它。也许程序员改变了IoC设置的测试,但忘记了#34;发布&#34;使用,然后甚至没有100%代码覆盖率的代码库将检测运行时错误。

可以在接口和服务中添加Init-function,但是服务的用户必须理解这一点,并且每次获得服务实例时都要记得调用init函数。该服务变得不那么自我解释,并且对于不正确的使用是开放的。如果方法不依赖于调用它们的顺序,那我就是最好的。

使其更安全的一种方法是在Ioc上使用Create-function。

public static class MyServiceIoc
{
    //...
    public IMyService CreateService(int initialValue)
    {
        var service = Container.Resolve<IMyService>();
        service.Init(initialValue);

    }
}

但如果您只查看服务及其界面,上述问题仍然适用。

有没有人有这个问题的强大解决方案?如何使用IoC以安全的方式将初始值传递给我的服务?

3 个答案:

答案 0 :(得分:3)

DI容器是基于反射的,并且基本上是弱类型的。问题比Primitive Dependencies更广泛 - 它到处都存在。

只要执行以下操作,您就已经失去了编译时的安全性:

IUnityContainer container = new UnityContainer();
container.RegisterType<IMyService, MyService>();
var service = container.Resolve<IMyService>(new ParameterOverrides
{                                                                                   
    {"initialValue", 42}
});

问题是您可以删除第二个语句,而代码仍会编译,但现在它不再有效:

IUnityContainer container = new UnityContainer();
var service = container.Resolve<IMyService>(new ParameterOverrides
{                                                                                   
    {"initialValue", 42}
});

请注意,缺乏编译时安全性与Concrete Dependency无关,而是涉及到DI Container。

这也不是一个Unity问题;它适用于所有DI容器。

There are cases where a DI Container may make sense,但在大多数情况下,Pure DI是一种更简单,更安全的选择:

IMyService service = new MyService(42);

在这里,如果其他人在您不在的时候更改了API,您将收到编译器错误。这很好:compiler errors give you more immediate feedback than run-time errors

顺便说一句,当你传入一个原始依赖并且无形地把它变成一个Concrete Dependency时,你就会让客户更难理解发生了什么。

我建议改为这样设计:

public class MyService : IMyService
{
    AnotherClass _anotherObject;
    // ...

    public MyService(AnotherClass anotherObject)
    {
        _anotherObject = anotherObject;
    }

    // ...
}

使用Pure DI,这仍然很容易且类型安全:

IMyService service = new MyService(new AnotherClass(42));

答案 1 :(得分:1)

  

如何使用IoC以安全的方式将初始值传递给我的服务?

您可以使用IUnityContainer.RegisterInstance方法在Unity中注册类型的构造函数时显式调用它:

container.RegisterInstance<IMyService>(new MyService(42));

这将为您提供您提到的编译时安全性,但成本是它只会被实例化一次,并且会立即创建(与首次请求时相反)。

您可以使用其中一个接受LifetimeManager类的方法重载来解决这个缺点。

答案 2 :(得分:0)

这取决于您的用例,但在 IoC 容器世界中,它可能如下所示:

{
   "$id": "1",
   "id": "d310b004-79a2-4661-2f90-08d8d25fec03"
   "orderItems": {
      $"id": "2",
      $"values": [
         {
            $"id": "3",
            "orderId": "d310b004-79a2-4661-2f90-08d8d25fec03",
            "orderItemId": "83d36eda-ba03-448c-e53c-08d8d25fec0b",
            "orderItem": {
               "id": "83d36eda-ba03-448c-e53c-08d8d25fec0b",
               "name": "My order item"
            },
            "order": {
               "$ref": "1" // reference to the order
            }
         }
      ]
   }
}

如果您的带有构造函数参数的类在您的代码之外(例如在第 3 方库中),您可以使用适配器。

public class MyService : IMyService
{
    int _initialValue;
    // ...

    public MyService(IConfigurationService configurationService)
    {
        _initialValue = configurationService.GetInitialValueForMyService();
    }
    
    // ...
}

然后像这样在 IoC 容器中注册适配的类:

public class AdaptedMyService : MyService
{
    public AdaptedMyService(IConfigurationService configurationService)
        : base(configurationService.GetInitialValueForMyService())
    {
    }
}