C# 什么是 Ninject,什么时候使用?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/17375234/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
What is Ninject and when do you use it?
提问by bachkoi32
I have been helping a few friends on a project and there is a class that uses Ninject. I am fairly new to C# and I have no idea what that class is doing, which is why I need to understand Ninject. Can anyone explain what Ninject is and when does one use it(with example if possible)? Or if you can point to some links that would be great too.
我一直在帮助几个朋友做一个项目,有一个使用 Ninject 的类。我对 C# 相当陌生,我不知道该类在做什么,这就是为什么我需要了解 Ninject。谁能解释一下 Ninject 是什么以及什么时候使用它(如果可能的话,举个例子)?或者,如果您可以指出一些也很棒的链接。
I tried this question: Ninject tutorials/documentations?but it didn't really help a beginner like me.
我试过这个问题:Ninject 教程/文档?但它并没有真正帮助像我这样的初学者。
回答by Sklivvz
Ninject is an Inversion of Control container.
Ninject 是一个控制反转容器。
What does it do?
它有什么作用?
Suppose you have a Carclass that depends on a Driverclass.
假设你有一个Car依赖于一个Driver类的类。
public class Car
{
public Car(IDriver driver)
{
///
}
}
In order to use the Carclass you build it like so:
为了使用Car该类,您可以像这样构建它:
IDriver driver = new Driver();
var car = new Car(driver);
A IoC containter centralizes the knowledge about how to build classes. It is a central repository that knows a few things. For example, it knows that the concrete class that you need to use to build a car is a Driverand not any other IDriver.
IoC 容器集中了有关如何构建类的知识。它是一个知道一些事情的中央存储库。例如,它知道您需要用来构建汽车的具体类是 aDriver而不是任何其他IDriver.
For example, if you are developing a MVC application, you can tell Ninject how to build your controllers. You do so by registering which concrete classes satisfy specific interfaces. At run time Ninject will figure out which classes are needed to build the required controller, and all behind the scenes.
例如,如果您正在开发 MVC 应用程序,您可以告诉 Ninject 如何构建您的控制器。您可以通过注册哪些具体类满足特定接口来实现。在运行时,Ninject 将找出构建所需控制器所需的类,以及所有幕后工作。
// Syntax for binding
Bind<IDriver>().To<Driver>();
This is beneficial because it lets you build systems that are more easily unit testable. Suppose that Driverencapsulates all database access for Car. In a unit test for Car you can do this:
这是有益的,因为它可以让您构建更容易进行单元测试的系统。假设Driver封装了Car. 在 Car 的单元测试中,您可以执行以下操作:
IDriver driver = new TestDriver(); // a fake driver that does not go to the db
var car = new Car(driver);
There are entire frameworks that take care of automatically creating testing classes for you and they are called mocking frameworks.
有完整的框架可以为您自动创建测试类,它们被称为模拟框架。
For more information:
想要查询更多的信息:
回答by Viktor Lova
Ninject is dependency injector for .NET, practical realisation of pattern Dependency Injection (form of Inversion of Control pattern).
Ninject 是.NET 的依赖注入器,是模式依赖注入(控制反转模式的形式)的实际实现。
Suppose you have two classes DbRepositoryand Controller:
假设你有两个类DbRepository和Controller:
class Controller {
private DbRepository _repository;
// ... some methods that uses _repository
}
class DbRepository {
// ... some bussiness logic here ...
}
So, now you have two problems:
所以,现在你有两个问题:
You must initialize
_repositoryto use it. You have several options for doing this:- Manually, within the constructor. But what if the constructor of DbRepository changes? You would need to rewrite your
Controllerclass because code it's dependent upon was changed. It's not hard if you have only oneController, but if you have a couple of classes that have a dependency on yourRepositoryyou have a real problem. You can use a service locator or factory. But now you have a dependency on your service locator. You have a global service locator and all code must use it. How you will you change the behavior of your service locator when you need to use it in one part of your code for activation logic but for something else in another part of your code? There is only one way - passing the service locator through constructors. But with more and more classes you will need to pass it more and more times. Anyway, it's a good thought but in the long run, it's a bad idea.
class Controller { private DbRepository _repository; public Controller() { _repository = GlobalServiceLocator.Get<DbRepository>() } // ... some methods that uses _repository }You can use dependency injection. Look at the code:
class Controller { private IRepository _repository; public Controller(IRepository repository) { _repository = repository; } }Now when you need your controller you write:
ninjectDevKernel.Get<Controller>();orninjectTestKernel.Get<Controller>();. You can switch beetween dependency resolvers as fast as you want. See? It's simple, you don't need to write a lot.
- Manually, within the constructor. But what if the constructor of DbRepository changes? You would need to rewrite your
You can't create unit tests for it. Your
Controllerhas a dependency onDbRepositoryand if you want to test some method that uses repository, your code will go to the database and ask it for data. That's slow, very slow. If your code inDbRepositorychanges, your unit test onControllerwill fall. Only integration test must warn you of 'problems' in this case. What you need in unit tests - is to isolate your classes and test only one class in one test (in ideal - only one method). If yourDbRepositorycode fails, you will think thatControllercode failed - and that's bad (even if you have tests forDbRepositoryandController- they both will fail and you can start from the wrong place). It takes a lot of time to determine where the error really is. You need to know that class A is ok, and it was class B where something failed.When you want to replace
DbRepositorywith something else in all your classes, you have to do a lot of work.You can't easily control the lifetime of
DbRepository. An object of this class is created on initialization ofControllerand deleted whenControlleris deleted. There is no sharing between different instances of theControllerclass and there is no sharing between other classes. With Ninject you can simply write:kernel.Bind<IRepository>().To<DbRepository>().InSingletonScope();
您必须初始化
_repository才能使用它。为此,您有多种选择:- 手动,在构造函数中。但是如果 DbRepository 的构造函数改变了呢?您需要重写您的
Controller类,因为它所依赖的代码已更改。如果您只有一个Controller,这并不难,但是如果您有几个依赖于您的类,那么您Repository就会遇到真正的问题。 您可以使用服务定位器或工厂。但是现在您依赖于您的服务定位器。您有一个全局服务定位器,所有代码都必须使用它。当您需要在代码的一部分用于激活逻辑但在代码的另一部分使用它时,您将如何更改服务定位器的行为?只有一种方法 - 通过构造函数传递服务定位器。但是随着越来越多的课程,你将需要通过它越来越多的时间。无论如何,这是一个好主意,但从长远来看,这是一个坏主意。
class Controller { private DbRepository _repository; public Controller() { _repository = GlobalServiceLocator.Get<DbRepository>() } // ... some methods that uses _repository }您可以使用依赖注入。看代码:
class Controller { private IRepository _repository; public Controller(IRepository repository) { _repository = repository; } }现在,当您需要控制器时,您可以编写:
ninjectDevKernel.Get<Controller>();或ninjectTestKernel.Get<Controller>();. 您可以根据需要在依赖项解析器之间快速切换。看?很简单,你不需要写很多。
- 手动,在构造函数中。但是如果 DbRepository 的构造函数改变了呢?您需要重写您的
你不能为它创建单元测试。您
Controller依赖于DbRepository并且如果您想测试使用存储库的某些方法,您的代码将转到数据库并要求它提供数据。这很慢,非常慢。如果你的代码DbRepository发生变化,你的单元测试Controller就会失败。在这种情况下,只有集成测试必须警告您“问题”。您在单元测试中需要什么 - 是隔离您的类并在一次测试中仅测试一个类(理想情况下 - 只有一种方法)。如果你的DbRepository代码失败了,你会认为Controller代码失败了——这很糟糕(即使你对DbRepository和Controller- 他们都会失败,你可以从错误的地方开始)。确定错误的真正位置需要花费大量时间。您需要知道 A 班没问题,而 B 班出了点问题。当你想在你
DbRepository所有的类中用其他东西替换时,你必须做很多工作。您无法轻松控制
DbRepository. 此类的对象在初始化Controller时创建并在删除时Controller删除。Controller类的不同实例之间没有共享,其他类之间也没有共享。使用 Ninject,您可以简单地编写:kernel.Bind<IRepository>().To<DbRepository>().InSingletonScope();
A special feature of dependency injection - agile development! You describe that your controller uses a repository with interface IRepository. You don't need to write DbRepository, you can simply create a MemoryRepositoryclass and develop Controllerwhile another person develops DbRepository. When work on DbRepositoryis finished, you just rebind in your dependency resolver that default IRepositoryis now DbRepository. Have a lot of controllers? All of them will now use DbRepository. That's cool.
依赖注入的一个特点——敏捷开发!您描述您的控制器使用带有 interface 的存储库IRepository。你不需要写DbRepository,你可以简单地创建一个MemoryRepository类并Controller在另一个人开发的同时进行开发DbRepository。DbRepository完成工作后,您只需重新绑定默认IRepository为 now 的依赖项解析器DbRepository。有很多控制器吗?他们现在都将使用DbRepository. 这很酷。
Read more:
阅读更多:
回答by JerryGoyal
Other answers are great but I would also like to point out this Implementing Dependency Injection using Ninjectarticle.
This is one of the best articles I ever read which explains Dependency Injection and Ninject with a very elegant example.
其他答案很好,但我也想指出这篇使用 Ninject 实现依赖注入的文章。
这是我读过的最好的文章之一,它用一个非常优雅的例子解释了依赖注入和 Ninject。
Here's the snippet from the article:
这是文章的片段:
Below Interface will be implemented by our (SMSService) and (MockSMSService), basically the new Interface (ISMSService) will expose the same behaviors of both services as the code below:
下面的接口将由我们的 (SMSService) 和 (MockSMSService) 实现,基本上新接口 (ISMSService) 将公开两个服务的相同行为,如下代码:
public interface ISMSService
{
void SendSMS(string phoneNumber, string body);
}
(SMSService) implementation to implement the (ISMSService) interface:
(SMSService) 实现来实现 (ISMSService) 接口:
public class SMSService : ISMSService
{
public void SendSMS(string mobileNumber, string body)
{
SendSMSUsingGateway(mobileNumber, body);
}
private void SendSMSUsingGateway(string mobileNumber, string body)
{
/*implementation for sending SMS using gateway*/
Console.WriteLine("Sending SMS using gateway to mobile:
{0}. SMS body: {1}", mobileNumber, body);
}
}
(MockSMSService) with totally different implementation using the same interface:
(MockSMSService) 使用相同的接口具有完全不同的实现:
public class MockSMSService :ISMSService
{
public void SendSMS(string phoneNumber, string body)
{
SaveSMSToFile(phoneNumber,body);
}
private void SaveSMSToFile(string mobileNumber, string body)
{
/*implementation for saving SMS to a file*/
Console.WriteLine("Mocking SMS using file to mobile:
{0}. SMS body: {1}", mobileNumber, body);
}
}
we need to implement a change to our (UIHandler) class constructor to pass the dependency through it, by doing this, the code which uses the (UIHandler) can determine which concrete implementation of (ISMSService) to use:
我们需要对我们的 (UIHandler) 类构造函数进行更改以通过它传递依赖项,通过这样做,使用 (UIHandler) 的代码可以确定要使用 (ISMSService) 的具体实现:
public class UIHandler
{
private readonly ISMSService _SMSService;
public UIHandler(ISMSService SMSService)
{
_SMSService = SMSService;
}
public void SendConfirmationMsg(string mobileNumber) {
_SMSService.SendSMS(mobileNumber, "Your order has been shipped successfully!");
}
}
Now, we have to create a separate class (NinjectBindings) which inherits from (NinjectModule). This class will be responsible to resolve dependencies at run time, then we'll override the load event which is used to configure the binding in it. The nice thing about Ninject is that we do not need to change our code in (ISMSService), (SMSService), and (MockSMSService).
现在,我们必须创建一个单独的类 (NinjectBindings),它继承自 (NinjectModule)。此类将负责在运行时解析依赖项,然后我们将覆盖用于配置其中的绑定的 load 事件。Ninject 的好处是我们不需要更改 (ISMSService)、(SMSService) 和 (MockSMSService) 中的代码。
public class NinjectBindings : Ninject.Modules.NinjectModule
{
public override void Load()
{
Bind<ISMSService>().To<MockSMSService>();
}
}
Now in UI form code, we'll use the binding for Ninject which will determine which implementation to use:
现在在 UI 表单代码中,我们将使用 Ninject 的绑定来确定要使用的实现:
class Program
{
static void Main(string[] args)
{
IKernel _Kernal = new StandardKernel();
_Kernal.Load(Assembly.GetExecutingAssembly());
ISMSService _SMSService = _Kernal.Get<ISMSService>();
UIHandler _UIHandler = new UIHandler(_SMSService);
_UIHandler.SendConfirmationMsg("96279544480");
Console.ReadLine();
}
}
Now the code is using the Ninject Kernal to resolve all chain of dependencies, if we want to use the real service (SMSService) in Release mode (on production environment) instead of the mock one, we need to change on the Ninject binding class (NinjectBindings) only to use the right implementation or by using the #if DEBUG directive as below:
现在代码使用Ninject Kernal来解决所有依赖链,如果我们想在Release模式下(在生产环境中)使用真实服务(SMSService)而不是模拟服务,我们需要在Ninject绑定类上进行更改( NinjectBindings) 仅用于使用正确的实现或使用 #if DEBUG 指令,如下所示:
public class NinjectBindings : Ninject.Modules.NinjectModule
{
public override void Load()
{
#if DEBUG
Bind<ISMSService>().To<MockSMSService>();
#else
Bind<ISMSService>().To<SMSService>();
#endif
}
}
Now our binding class (NinjectBindings) is living on the top of all our execution code and we can control the configuration easily in once place.
现在我们的绑定类 (NinjectBindings) 位于我们所有执行代码的顶部,我们可以在一个地方轻松控制配置。
Also, see What is Inversion of Control?some very simple examples are mentioned to understand IoC.
另请参阅什么是控制反转?提到了一些非常简单的例子来理解 IoC。

