0

I'm currently using Ninject to handle DI on a C#/.Net/MVC application. When I trace the creation of instances of my services, I find that services are called and constructed quite a lot during a the life cycle, so I'm having to instantiate services and cache them, and then check for cached services before instantiating another. The constructors are sometimes quite heavy).

To me this seems ridiculous, as the services do not need unique constructor arguments, so instantiating them once is enough for the entire application scope.

What I've done as a quick alternative (just for proof-of-concept for now to see if it even works) is...

  • Created a static class (called AppServices) with all my service interfaces as it's properties.
  • Given this class an Init() method that instantiates a direct implementation of each service interface from my service library. This mimics binding them to a kernel if I was using Ninject (or other DI handler).

E.g.

public static class AppServices(){
  public IMyService MyService;
  public IMyOtherService MyOtherService;

  public Init(){
     MyService = new MyLib.MyService();
     MyOtherService =  new MyLib.MyOtherService();
  }
}
  • On App_Start I call the Init() method to create a list of globally accessible services that are only instantiated once.
  • From then on, every time I need an instance of a service, I get it from AppServices. This way I don't have to keep constructing new instances that I don't need.

E.g.

var IMyService _myService = AppServices.MyService;

This works fine and I haven't had ANY issues arise yet. My problem is that this seems way too simple. It is only a few lines of code, creating a static class in application scope. Being as it does exactly what I would need Ninject to do, but in (what seems to me for my purposes) a much cleaner and performance-saving way, why do I need Ninject? I mean, these complicated dependency injection handlers are created for a reason right? There must be something wrong with my "simple" interpretation of DI, I just can't see it.

Can any one tell me why creating a global static container for my service instances is a bad idea, and maybe explain exactly what make Ninject (or any other DI handler) so necessary. I understand the concepts of DI so please don't try and explain what makes it so great. I know. I want to know exactly what it does under the hood that is so different to my App_Start method.

Thanks

  • #1: *"Can any one tell me why creating a global static container for my service instances is a bad idea"*, #2: *"I understand the concepts of DI so please don't try and explain what makes it so great"*. IMHO if you really understood you wouldn't have to ask that question. How are you going to write tests for your application components if all of them reach into `AppServices` and grab stuff at their pleasure? – Jon Oct 23 '13 at 11:12
  • I'm not quite sure i understand: are you implementing the "caching" yourself or are you just describing what you "traced" what ninject is doing? Because i hope you know that you can just bind something .InSingletonScope() and thus ensure the ctor is only executed once. There's no need to implement that yourself. There's also other scopes for more advanced requirements (like .InNamedScope, .InRequestScope). Furthermore in most cases the checking whether it is cached has neglible performance impact. – BatteryBackupUnit Oct 23 '13 at 12:41
  • @jon Sure. Those are public fields, so the tests can overwrite them. – Kris Vandermotten Oct 23 '13 at 13:14
  • @KrisVandermotten: That is obvious so it should not be a surprise that it doesn't solve the problem, right? Static stuff is per-AppDomain, which means that you cannot run two tests in the same AppDomain (let's just say "process" from now on) *in parallel* if each one needs a different service. And they *will* want that -- some will want to run with the real service X, some will mock service X instead to test it. Where does that leave you? – Jon Oct 23 '13 at 13:19
  • @Jon Surely you can run different tests with different services, if you run them one after the other. If you want to run them in parallel, and each test executes (i.e. requests services) on a single thread (i.e. the thread on which the test runs), you can make those fields threadlocal. – Kris Vandermotten Oct 23 '13 at 13:28
  • @KrisVandermotten: If you run tests sequentially that could take forever. If you make the services per-thread (i.e. per-test) then you just won the battle but lost the war: the static registry was created precisely because we wanted to cache the services instead of creating new instances all the time. And in both cases the implementation starts reaching out into test-land by placing restrictions on your test runner. If you are still unconvinced I give up. – Jon Oct 23 '13 at 13:33
  • @Jon Per thread is not the same as per test. How do you think your DI container works? It is limited by the very same .NET limitations! And here's another question: of course I should be able to test my app. That is required. But I have other requirements too: cost-effective development, or efficient runtime execution for example, and high readability and maintainability. What if my performance requirements happen to be so high, that the DI cost is significant? DI was a means to an end, not a goal. Just like anything else, when it stands in the way of your goals, don't use it. – Kris Vandermotten Oct 23 '13 at 13:41
  • @KrisVandermotten: I have no desire to argue further even though I strongly disagree with some of what you said. – Jon Oct 23 '13 at 13:46
  • @Jon That is perfectly OK. So I conclude: OP asks for arguments, and all you can say is "if you really understood you wouldn't have to ask" and "I have no desire to argue". Sounds like you have an almost religious believe that DI is a Good Thing, without being able to explain why that is. You know what? Maybe, just maybe, sometimes it isn't. OP's question is: "Can any one tell me [...] what make Ninject (or any other DI handler) so necessary." That's a very good question, and you apparently can't answer it. – Kris Vandermotten Oct 23 '13 at 14:04
  • @KrisVandermotten: Correction: I apparently have no use *for arguing with you*. "Those are public fields, so the tests can overwrite them" shows a lack of basic understanding of why global state is bad. That's all I care to say at this point, have a good day. – Jon Oct 23 '13 at 14:08
  • Thanks guys. I hadn't really thought too much about detailed testing. For any basic test I would run, then I would have overwritten the public property of AppServices (as you mention @KrisVandermotten). I can definitely see why on a bigger application running a lot of tests in parallel that this becomes a problem. Also, I hadn't realized I can use InSingletonScope to only construct them once. Thanks @jon. It makes more sense once I start thinking about unit testing. It's probably quite evident from my reply that I won't be doing much. I am just using it for swapping out implementations. – user2910842 Oct 23 '13 at 14:24
  • @KrisVandermotten - Thanks for your input. As you say, I'm asking for points of view as I clearly am not that well versed myself. These are all good points, and starts to illustrate why these frameworks are built, as my static implementation has flaws that will probably bite me in the arse later. For this particular project, the cost or scale doesn't warrant a big DI budget, or I get someone who knows it much better than I do. Thanks again for the info guys. – user2910842 Oct 23 '13 at 14:40

1 Answers1

1

Your question needs to be divided into two questions:

  1. Is it really wrong to use the singleton pattern instead to inject dependencies?
  2. Why do I need an IoC container?

1)

There are many reasons why you should not use the singleton pattern. Here are some of the major ones:

Testability

Yes you can test with static instances. But you can't test Isolated (FIRST). I have seen projects that searched a long time why tests start failing for no obvious reason until they realized that it is due to tests that were run in a different order. When you had that problem once you will always want your tests to be as isolated as possible. Static values couples tests.

This gets even worse when you also do integration/spec testing additional to unittesting.

Reusability

You can't simply reuse your components in other projects. Other projects will have to use that concept as well even if they might decide to use an IoC container.

Or you can't create another instance of your component with different dependencies. The components dependencies will be hard wired to the instances in your AppServices. You will have to change the components implementation to use different dependencies.

2) Doing DI does not mean that you have to use any IoC container. You can implement your own IDependencyResolver that creates your controllers manually and injects the same instance of your services wherever they are required. IoC containers use some performance but they simplyfy the creation of your object trees. You will have to decide yourself what matters more performance or simpler creation of your controllers.

Remo Gloor
  • 32,665
  • 4
  • 68
  • 98