C# 依赖注入容器的意义是什么?

C# 依赖注入容器的意义是什么?,c#,dependency-injection,C#,Dependency Injection,使用.NETCore,您可以注册“服务”,据我所知,这仅仅意味着您可以将类型注册到具体的类 因此,我决定是时候学习DI并练习它了。我理解这个概念,通过测试它是非常有益的。然而,让我困惑的是注册服务的想法以及它是否真的需要 例如,如果我有: public class MyClass { public MyClass(IDataContext) { ... store it } } 这意味着我可以注入实现IDataContext的任何类,允许在测试中使用

使用.NETCore,您可以注册“服务”,据我所知,这仅仅意味着您可以将类型注册到具体的类

因此,我决定是时候学习DI并练习它了。我理解这个概念,通过测试它是非常有益的。然而,让我困惑的是注册服务的想法以及它是否真的需要

例如,如果我有:

public class MyClass
{
    public MyClass(IDataContext)
    {
         ... store it 
    }
}
这意味着我可以注入实现IDataContext的任何类,允许在测试中使用伪造和MOQ。但是为什么我要注册一个服务并在启动时将IDataContext映射到一个具体的类呢?在其他方法中仅使用以下方法是否有问题:

DataContext dc = new DataContext(); // concrete
var c = new MyClass(dc);
编辑


这个问题是关于使用容器(服务)的问题,而不是为什么在构造函数中使用接口

现在,您将代码放在那些类中

public class MyService
{
    public void DoSomething()
    {
        DataContext dc = new DataContext(); // concrete
        var c = new MyClass(dc);
        c.DoSomething();
    }
}
DataContext
MyClass
具有硬依赖性。因此,您不能单独测试
MyService
。班级不应该关心其他班级如何做他们做的事情,他们应该只关心他们做他们说他们要做的事情。这就是我们使用接口的原因。这是关注点的分离。一旦实现了这一点,就可以独立地对任何代码进行单元测试,而不依赖于外部代码的行为

在一个位置预先注册依赖项也更干净,这意味着您可以通过更改一个位置来交换依赖项,而不是查找所有用法并单独更改它们

在顶部的代码示例中,
MyService
要求同时使用
DataContext
MyClass
。相反,它应该是这样的:

public class MyService
{
    private readonly IMyClass _myClass;

    public MyService(IMyClass myClass)
    {
        _myClass = myClass;
    }

    public void DoSomething()
    {
        _myClass.DoSomething();
    }
}

public interface IMyClass
{
    void DoSomething();
}

public class MyClass : IMyClass
{
    private readonly IDataContext _context;

    public MyClass(IDataContext context)
    {
        _context = context;
    }
    public void DoSomething()
    {
        _context.SaveSomeData();
    }
}
现在,
MyService
完全不依赖于
DataContext
,它不需要担心,因为这不是它的工作。但它确实需要满足IMyClass的要求,但它不关心如何实现它
MyService.DoSomething()
现在可以进行单元测试,而不依赖于其他代码的行为

如果您没有使用容器来处理满足依赖关系的问题,那么您很可能在类中引入了硬依赖关系,这首先破坏了针对接口进行编码的整个要点

隔离测试很重要。如果您要测试多个有限的代码段,那么这不是单元测试。这是一个集成测试(由于不同的原因,它们有自己的价值)。单元测试使验证有限的代码块是否按预期工作变得简单快捷。当单元测试没有通过时,您就知道问题所在,不必费劲地搜索就可以找到它。因此,如果单元测试依赖于其他类型,或者甚至其他系统(在本例中,
DataContext
是特定于特定数据库的),那么我们不能在不接触数据库的情况下测试
MyService
。这意味着数据库必须处于特定的测试状态,这意味着测试可能不是幂等的(您不能反复运行它并期望相同的结果)


更多信息,我建议你看米格尔·卡斯特罗的。他说的最好的一点是,如果你必须使用
new
来创建一个对象的实例,那么你已经将事物紧密地耦合在一起了。避免使用
new
,依赖项注入是一种可以避免这种情况的模式。(对POCO模型使用
new
并不总是坏事,我对使用
new
很满意)。

您可以手动注入依赖项。然而,这可能会得到一个非常乏味的任务。如果您的服务变得更大,您将获得更多的依赖项,其中每个依赖项本身可以有多个依赖项

如果更改依赖项,则需要调整所有用法。DI容器的主要优点之一是,容器将完成所有依赖项解析。不需要人工操作。只需注册该服务,并在任何地方使用它,以及使用频率

对于小项目来说,这似乎是太多的开销,但如果您的项目增长了一点,您将非常感激这一点

对于紧密相关且不太可能更改的固定依赖项,可以手动注入它们

使用DI容器还有另一个优点。DI容器将控制其服务的生命周期。服务可以是单例的、暂时的(每个请求将获得一个新实例),也可以具有限定的生命周期

例如,如果您有事务工作流。作用域可能与事务匹配。在事务中,对服务的请求将返回相同的实例

下一个事务将打开一个新范围,因此将获得新实例。
这允许您放弃或提交一个事务的所有实例,但防止后续事务使用上一个事务的资源。

如果您正确,可以手动创建所有实例。在小项目中,这是一种惯例。项目中链接类的位置称为复合根,您所做的是构造函数注入


IoC库可以简化此代码,特别是考虑到生命周期范围和组注册等复杂情况。

控制反转(构建对象)

这种模式的思想是,当您想要构造一个对象时,您只需要知道对象的类型,而不需要知道它的依赖项或参数


依赖注入

例如,该模式使您能够直接将对象注入构造函数,从而进一步实现了控制模式的反转

同样,您只需要知道要获取的对象的类型,并且依赖项容器将注入一个对象

您也不需要知道是否构造了一个新对象,或者是否获得了一个现成的现有引用

最常用的一种