The Quick & Dirty
There have been a lot of good reviews written for NDepend, so for the sake of efficiency, I'll get straight to the point: NDepend is an amazing tool. It lets you graphically inspect your assemblies and it lets you pinpoint the spots in your code base that need refactoring.
So would I recommend buying NDepend Professional? Yes.
Is there something that I didn't like about NDepend that I think the users should know about? Yes, and that's exactly what I'll talk about.
"So what's not to like about NDepend?"
My only problem with NDepend is that its CQL query engine can only be accessed from the Visual NDepend application. NDepend does let you embed CQL attributes in your applications, but the problem is that I shouldn't have to force each one of my applications to have a dependency on NDepend just because I want to have some code metrics calculations in my build process. Of course, there are a few ways that I can work around that limitation, but the point is that I shouldn't have to make a workaround for something that should have been there in the first place.
In other words, what NDepend sorely needs right now is a publicly-accessible CQL engine API. If NDepend is truly a tool built by developers for developers, then it should have an API that other developers can use to analyze their applications. It's not enough to throw them a rudimentary command-line tool that spits out code metrics in XML--that pretty much leaves developers to write code against the "shadow" of the CQL API rather than write against the CQL API itself, and for me, that just doesn't quite cut it.
Don't get me wrong--the Visual NDepend winforms application is an awesome app--but I'd rather be able to filter through all that information using a single API call rather than have to manually sift through several windows just to find the information I need.
Now the reason why I'm harping so much about the lack of a public CQL API is that my company uses NDepend Professional in its command line builds to ensure that our builds fail when the Cyclomatic Complexity score exceeds 2.5 in any of our projects. The problem is that we want our code quality inspections to be completely automated, and we can't do that unless we have access to the CQL API and can generate CQL queries at runtime. The maximum Cyclomatic Complexity score might prevent our developers from writing complicated code, but there's far more to code inspections than just looking at a single score. What we need is a way to look deeper into the quality of our code base using the hypothetical CQL API with the same ease that Visual NDepend users can browse through their code.
So in the end, I still recommend NDepend Pro as definite buy, but it still lacks a few key features that would make me give Patrick Smacchia's tool the gushing review that other bloggers have given it in their respective reviews. Overall, it's definitely a great tool, but it's not without a few shortcomings.
[Public Disclosure: Thanks to Patrick Smacchia for providing me with a free NDepend Pro license for this review.]
Thursday, July 16, 2009
Wednesday, April 15, 2009
Introducing Hiro, the World's Fastest IOC Container, Part II: The Little Feature Set That Could
The Art of Lean
Now that everyone knows just how fast Hiro can be, the next question you might be asking yourself is, "What features will it support?"
The simplest answer that I can give is that Hiro is that it will implement as little features as possible--in fact, Hiro will implement so little features that it might turn away your traditional "dynamic" IOC user. Here's the features that Hiro will implement:
-Named/Anonymous Static Component Registration. This means that you'll be able to register your services using a service name, service type, and the implementing type.
-Convention Over Configuration for Registration and Injection.. In addition to its support for programmatic registration, Hiro will be able to scan your assemblies and automatically infer:
Believe it or not, this is the only code that you need to use to configure the dependencies for any assembly of any given size. What makes this even more interesting is the fact that all of this is done statically at (container) compile time, not runtime. This means that unlike the current generation of IOC containers, Hiro will not waste any time trying to rediscover your configuration, and based on the benchmark results from my previous post, the performance numbers are nothing short of being dramatically one-sided.
-Transient/Singleton instances. Aside from performance, however, Hiro has to be able to create both Transient (that is, plain old object instances created with the New operator) and Singleton instances.
-Property/Constructor Injection. Hiro will implement both property and constructor injection by default. What makes this even more interesting is that like everything else with Hiro, all property and constructor injection calls will all be precompiled into IL, meaning that there will be no performance issues when using Hiro.
-Stateless. Each Hiro-compiled container instance will not have any private local data (or even shared data) that will distinguish it from another compiled container of the same type. This means that you can scale Hiro's compiled containers across multiple cores AND multiple threads without using a single semaphore, mutex, or lock statement (in C#).
"But wait, your container isn't a REAL container until you implement feature X!"
Some would argue that Hiro would have to implement a minimum "baseline" feature set in order to be considered a "commercial" quality IOC container. However, my counterargument there is that it's this same "fat feature" mindset that got these IOC containers (including LinFu) into this speed problem in the first place. Secondly, if you're experienced enough about IOC to understand the significance of what Hiro does, then it's safe to assume that you're a user that falls into at least one of the following categories:
Assuming that you're an IOC container author, it would be pointless for me to implement something in Hiro that you've probably rolled into your own framework, and given that you're skilled enough to write your own framework, it would be practically trivial for you to plug Hiro into your own framework and reap the performance benefits, and there's clearly no reason to reinvent the wheel here if you somehow did a better job than I did in implementing "feature X".
Now, if you think you're a user that falls into the second category, there's a good chance that you've pretty much decided to stick to the favorite framework of your choice, and like the other IOC container authors, there's really nothing that I can do for you unless you decide to plug in Hiro into your favorite container.
So between these two types of users, who do you think Hiro is written for?
Here's my answer: Neither one of them. Hiro is written for the average developer who wants to get started with an IOC container and doesn't have time to "geek out" over the latest and greatest features of an IOC container framework. Given that there are probably far better IOC container developers than myself, I've pretty much decided to skip the "my container is better than your container" religious wars and focus on what really matters: the end users.
The Pareto Pleasure Principle
Hiro doesn't need to implement 80% of the expected features of an IOC container in order to be useful--instead, it only has to implement the other 20% of the overall features that (in my opinion) people will actually use. In the end, if I can help those people get their jobs done in the simplest possible way without forcing them to wade through the "awesomeness" of my framework, then I would call Hiro a success, and at the end of the day, that's really all that matters.
Now that everyone knows just how fast Hiro can be, the next question you might be asking yourself is, "What features will it support?"
The simplest answer that I can give is that Hiro is that it will implement as little features as possible--in fact, Hiro will implement so little features that it might turn away your traditional "dynamic" IOC user. Here's the features that Hiro will implement:
-Named/Anonymous Static Component Registration. This means that you'll be able to register your services using a service name, service type, and the implementing type.
-Convention Over Configuration for Registration and Injection.. In addition to its support for programmatic registration, Hiro will be able to scan your assemblies and automatically infer:
- The list of available services
- The list of concrete types that implement those services
- The list of properties that can be injected
- The constructors that will be used for constructor injection
- The services that will be injected into each parameter during a constructor injection call.
var loader = new DependencyMapLoader();
var map = loader.LoadFrom(AppDomain.CurrentDomain.BaseDirectory, "Yourlibrary.dll");
IContainerCompiler compiler = new ContainerCompiler();
var microContainer = compiler.Compile(map);
// Do something with the container here
Believe it or not, this is the only code that you need to use to configure the dependencies for any assembly of any given size. What makes this even more interesting is the fact that all of this is done statically at (container) compile time, not runtime. This means that unlike the current generation of IOC containers, Hiro will not waste any time trying to rediscover your configuration, and based on the benchmark results from my previous post, the performance numbers are nothing short of being dramatically one-sided.
-Transient/Singleton instances. Aside from performance, however, Hiro has to be able to create both Transient (that is, plain old object instances created with the New operator) and Singleton instances.
-Property/Constructor Injection. Hiro will implement both property and constructor injection by default. What makes this even more interesting is that like everything else with Hiro, all property and constructor injection calls will all be precompiled into IL, meaning that there will be no performance issues when using Hiro.
-Stateless. Each Hiro-compiled container instance will not have any private local data (or even shared data) that will distinguish it from another compiled container of the same type. This means that you can scale Hiro's compiled containers across multiple cores AND multiple threads without using a single semaphore, mutex, or lock statement (in C#).
"But wait, your container isn't a REAL container until you implement feature X!"
Some would argue that Hiro would have to implement a minimum "baseline" feature set in order to be considered a "commercial" quality IOC container. However, my counterargument there is that it's this same "fat feature" mindset that got these IOC containers (including LinFu) into this speed problem in the first place. Secondly, if you're experienced enough about IOC to understand the significance of what Hiro does, then it's safe to assume that you're a user that falls into at least one of the following categories:
- You've probably written at least one IOC container framework, or
- You already are comfortable with an existing IOC container framework (such as Castle, Ninject, AutoFac, Unity, StructureMap, LinFu, etc) and you know enough to customize it to your needs.
Assuming that you're an IOC container author, it would be pointless for me to implement something in Hiro that you've probably rolled into your own framework, and given that you're skilled enough to write your own framework, it would be practically trivial for you to plug Hiro into your own framework and reap the performance benefits, and there's clearly no reason to reinvent the wheel here if you somehow did a better job than I did in implementing "feature X".
Now, if you think you're a user that falls into the second category, there's a good chance that you've pretty much decided to stick to the favorite framework of your choice, and like the other IOC container authors, there's really nothing that I can do for you unless you decide to plug in Hiro into your favorite container.
So between these two types of users, who do you think Hiro is written for?
Here's my answer: Neither one of them. Hiro is written for the average developer who wants to get started with an IOC container and doesn't have time to "geek out" over the latest and greatest features of an IOC container framework. Given that there are probably far better IOC container developers than myself, I've pretty much decided to skip the "my container is better than your container" religious wars and focus on what really matters: the end users.
The Pareto Pleasure Principle
Hiro doesn't need to implement 80% of the expected features of an IOC container in order to be useful--instead, it only has to implement the other 20% of the overall features that (in my opinion) people will actually use. In the end, if I can help those people get their jobs done in the simplest possible way without forcing them to wade through the "awesomeness" of my framework, then I would call Hiro a success, and at the end of the day, that's really all that matters.
Tuesday, April 7, 2009
Introducing Hiro, the World's Fastest IOC Container, Part I: Design Diary
Introduction
Have you ever had one of those moments where someone told you that your work sucked, and it inspired you to create something better? About a month ago,Alex Simkin sent me a message on the CodeProject forums for one of my articles saying that LinFu ranked second to last in performance among all the other containers, and that he was willing to show me the benchmark code that produced those numbers.
Eating the Humble Pie
Unfortunately, Alex was correct. Despite all of its features, LinFu landed a spot near the bottom of the pack, and needless to say, there had to be a better way to design a container so that it wouldn't have these bottlenecks.
"But...but...my container is dynamic and it's flexible!"
As an IOC container author myself, I've probably given that same excuse a dozen times over whenever someone complained that my framework was too slow. I never realized that the flexibility that I so touted in all my IOC articles was the same cause for all my performance headaches. Indeed, there had to be some way to improve these numbers, and at first, I thought adding more dynamic IL generation would solve the problem. After all, Lightweight Code Generation with DynamicMethod seems to be the trend nowadays among the other frameworks like Ninject, and that makes their code run faster, right?
Once again, I was wrong. DynamicMethods didn't make much of a performance impact because Ninject (which reportedly uses a lot of LCG its code) was actually the slowest among all of the IOC containers tested in the benchmark (Sorry Nate). Of course, this doesn't mean that the DynamicMethod approach is the cause of the slowdown; what it does suggest, however, is that piling more and more reflection onto the speed problem is not the solution. In addition, there were other frameworks in that benchmark (such as Funq) that didn't use any reflection at all, and yet, they still were taking significant performance hits on that benchmark. In fact, even the fastest among all the other containers--StructureMap--was still running forty-four times slower than the Plain/No Dependency Injection use case!
So the principle question is this: "Where is this bottleneck coming from, and how do I eliminate it?"
The Real Problem
As it turns out, the answer was staring me in the face all along: "It's the configuration, stupid", I thought to myself. The problem is that every major IOC container at the time of this post (such as Ninject, StructureMap, Unity, AutoFac, Castle, LinFu, etc) essentially has to trawl through each one of its dependencies just to instantiate a single service instance on every call, and practically no amount of optimization will ever compensate for the fact that they still have to "rediscover" any given part of an application's configuration in order to instantiate that one service instance. Needless to say, this rediscovery process wastes a huge amount of resources because these containers are actually "rediscovering" a configuration that (for all practical purposes) will rarely change between two successive method calls.
In layman's terms, this is akin to stopping and asking for directions at every intersection every time you want to leave your home to go to some other destination. There has to be some way to see the "whole map" and plan the trip ahead of time without having to stop for directions at every intersection. If you could plan all the possible routes on that trip ahead of time, then all the time you would have wasted asking for directions immediately vanishes.
In essence, that is what I did with Hiro. Hiro is an IOC container framework that reads the dependencies in your application ahead of time and actually compiles a custom IOC container that knows how to create those dependencies from your application itself. It uses absolutely no runtime reflection or runtime code generation, and since all your dependencies are discovered at compile time (that is, when the Hiro compiler runs), Hiro suffers zero performance penalties at runtime when instantiating your types.
Yes, you read that right: Hiro runs at 1:1 speed with a Plain/No DI configuration. Here's the results of the IOC container benchmark:

As you can see from the results above, the current crop of IOC containers (including LinFu) can only reach 2% of the speed of an application that does not use an IOC container. Now, let's take a look at Hiro's results:

If you don't believe it, then you can download and run the benchmarks yourself.
Like LinFu, Hiro is licensed under the terms of the LGPL, and you can preview the source code at this site. I'll also be starting a Hiro-contrib project, so if you want to add your own extensions, just email me at marttub@hotmail.com and I'll be more than happy to anyone who is interested. Thanks! :)
Have you ever had one of those moments where someone told you that your work sucked, and it inspired you to create something better? About a month ago,Alex Simkin sent me a message on the CodeProject forums for one of my articles saying that LinFu ranked second to last in performance among all the other containers, and that he was willing to show me the benchmark code that produced those numbers.
Eating the Humble Pie
Unfortunately, Alex was correct. Despite all of its features, LinFu landed a spot near the bottom of the pack, and needless to say, there had to be a better way to design a container so that it wouldn't have these bottlenecks.
"But...but...my container is dynamic and it's flexible!"
As an IOC container author myself, I've probably given that same excuse a dozen times over whenever someone complained that my framework was too slow. I never realized that the flexibility that I so touted in all my IOC articles was the same cause for all my performance headaches. Indeed, there had to be some way to improve these numbers, and at first, I thought adding more dynamic IL generation would solve the problem. After all, Lightweight Code Generation with DynamicMethod seems to be the trend nowadays among the other frameworks like Ninject, and that makes their code run faster, right?
Once again, I was wrong. DynamicMethods didn't make much of a performance impact because Ninject (which reportedly uses a lot of LCG its code) was actually the slowest among all of the IOC containers tested in the benchmark (Sorry Nate). Of course, this doesn't mean that the DynamicMethod approach is the cause of the slowdown; what it does suggest, however, is that piling more and more reflection onto the speed problem is not the solution. In addition, there were other frameworks in that benchmark (such as Funq) that didn't use any reflection at all, and yet, they still were taking significant performance hits on that benchmark. In fact, even the fastest among all the other containers--StructureMap--was still running forty-four times slower than the Plain/No Dependency Injection use case!
So the principle question is this: "Where is this bottleneck coming from, and how do I eliminate it?"
The Real Problem
As it turns out, the answer was staring me in the face all along: "It's the configuration, stupid", I thought to myself. The problem is that every major IOC container at the time of this post (such as Ninject, StructureMap, Unity, AutoFac, Castle, LinFu, etc) essentially has to trawl through each one of its dependencies just to instantiate a single service instance on every call, and practically no amount of optimization will ever compensate for the fact that they still have to "rediscover" any given part of an application's configuration in order to instantiate that one service instance. Needless to say, this rediscovery process wastes a huge amount of resources because these containers are actually "rediscovering" a configuration that (for all practical purposes) will rarely change between two successive method calls.
In layman's terms, this is akin to stopping and asking for directions at every intersection every time you want to leave your home to go to some other destination. There has to be some way to see the "whole map" and plan the trip ahead of time without having to stop for directions at every intersection. If you could plan all the possible routes on that trip ahead of time, then all the time you would have wasted asking for directions immediately vanishes.
In essence, that is what I did with Hiro. Hiro is an IOC container framework that reads the dependencies in your application ahead of time and actually compiles a custom IOC container that knows how to create those dependencies from your application itself. It uses absolutely no runtime reflection or runtime code generation, and since all your dependencies are discovered at compile time (that is, when the Hiro compiler runs), Hiro suffers zero performance penalties at runtime when instantiating your types.
Yes, you read that right: Hiro runs at 1:1 speed with a Plain/No DI configuration. Here's the results of the IOC container benchmark:

As you can see from the results above, the current crop of IOC containers (including LinFu) can only reach 2% of the speed of an application that does not use an IOC container. Now, let's take a look at Hiro's results:

If you don't believe it, then you can download and run the benchmarks yourself.
Like LinFu, Hiro is licensed under the terms of the LGPL, and you can preview the source code at this site. I'll also be starting a Hiro-contrib project, so if you want to add your own extensions, just email me at marttub@hotmail.com and I'll be more than happy to anyone who is interested. Thanks! :)
Saturday, February 28, 2009
Common Service Locator Adapter for LinFu 2.0 Released!
Now that LinFu.IOC 2.0 is officially out, it's time to join the rest of the IOC container pack and let users try out LinFu without tying them to any specific IOC container API. The adapter for LinFu makes it possible to use the Common Service Locator interfaces to make your applications completely container-agnostic, so if you haven't tried LinFu yet, download the adapter, and give it a shot. :)
Thursday, November 6, 2008
LinFu.DynamicProxy 1.01 is now a part of NHibernate!
After passing all 1500+ tests in the NHibernate test suite, LinFu.DP1 will now be an official part of the NHibernate distribution!
It's really an honor to be a part of a project that is so well-known in the .NET community, and I really want to thank the NHibernate team for giving LinFu a chance.
There were a lot of developers who shied away from LinFu DP1 for the lack of unit tests, and this proves that LinFu DP 1.0 works as advertised, regardless of whether or not the tests existed. You now have 1500+ reasons to try it out without worrying about any problems, so give it a shot, and I'll always be here to answer your questions if anything breaks.
[Disclaimer: That being said, I'm now a TDD convert and I no longer develop anything without a battery of automated unit tests--but it's still an big ego-booster to know that your code works exactly as it's supposed to work :) ]
It's really an honor to be a part of a project that is so well-known in the .NET community, and I really want to thank the NHibernate team for giving LinFu a chance.
There were a lot of developers who shied away from LinFu DP1 for the lack of unit tests, and this proves that LinFu DP 1.0 works as advertised, regardless of whether or not the tests existed. You now have 1500+ reasons to try it out without worrying about any problems, so give it a shot, and I'll always be here to answer your questions if anything breaks.
[Disclaimer: That being said, I'm now a TDD convert and I no longer develop anything without a battery of automated unit tests--but it's still an big ego-booster to know that your code works exactly as it's supposed to work :) ]
Sunday, September 21, 2008
LinFu IoC 2.0 Reaches an Important Milestone
Making the Grade
After nearly two months of hard work, I am pleased to announce the impending release of LinFu.IoC v2.0! Today marks an amazing milestone in the development of LinFu's new IoC container because as of the latest build (revision 258), LinFu is the first IoC container framework to pass ALL tests in both the "MustHave" and the "ShouldHave" categories in the latest comparison between the following IoC Frameworks:
What this means is that LinFu.IoC (formerly Simple.IoC) has gone from an undocumented and untested inversion of control container to a fully documented, heavily tested, feature-laden container that is capable of performing all of the tasks that one would expect from a commercial-grade inversion of control container.
In addition to the keeping the code as clean and as compact as possible, I spent countless hours ensuring that every single method, class, interface, property and enum is fully documented, regardless of whether or not that item was marked as public, internal, or private. I love my code and I love what I do, and I hope that shows in the code that I write.
The best part about all this is that LinFu's IoC container framework weighs in at only 94KB, making it the smallest IoC framework among its brethen, with the Autofac container falling at a close 110KB.
Within the next few weeks, I'll be publishing an article on CodeProject that details all the features that you can expect from LinFu.IoC 2.0, and I will time the release so that it coincides with the actual CodeProject article itself.
Meanwhile, stay tuned!
EDIT: I realize that this post is quite scant on details so if you want to dive straight into the LinFu.IoC source, you can just go here. If you want to take a look at the IoC framework comparison project, click here. The code practically speaks for itself. Enjoy!
After nearly two months of hard work, I am pleased to announce the impending release of LinFu.IoC v2.0! Today marks an amazing milestone in the development of LinFu's new IoC container because as of the latest build (revision 258), LinFu is the first IoC container framework to pass ALL tests in both the "MustHave" and the "ShouldHave" categories in the latest comparison between the following IoC Frameworks:
- AutoFac
- Castle
- LinFu
- Ninject
- StructureMap
- Spring.NET
- Unity
What this means is that LinFu.IoC (formerly Simple.IoC) has gone from an undocumented and untested inversion of control container to a fully documented, heavily tested, feature-laden container that is capable of performing all of the tasks that one would expect from a commercial-grade inversion of control container.
In addition to the keeping the code as clean and as compact as possible, I spent countless hours ensuring that every single method, class, interface, property and enum is fully documented, regardless of whether or not that item was marked as public, internal, or private. I love my code and I love what I do, and I hope that shows in the code that I write.
The best part about all this is that LinFu's IoC container framework weighs in at only 94KB, making it the smallest IoC framework among its brethen, with the Autofac container falling at a close 110KB.
Within the next few weeks, I'll be publishing an article on CodeProject that details all the features that you can expect from LinFu.IoC 2.0, and I will time the release so that it coincides with the actual CodeProject article itself.
Meanwhile, stay tuned!
EDIT: I realize that this post is quite scant on details so if you want to dive straight into the LinFu.IoC source, you can just go here. If you want to take a look at the IoC framework comparison project, click here. The code practically speaks for itself. Enjoy!
Sunday, August 3, 2008
Making Simple.IoC Even More Simple
Believe it or not, the following code represents most of LinFu.IoC's functionality when managing service instances:
As you can see, there's nothing special about this code. Some might even scoff at it since it's too minimalistic to be useful. After all, one might say that an IoC container has far more responsibilities than just object instantiation. Ninject, for example has features such as contextual binding and method interception. Surely this isn't all there is to LinFu.IoC's features, is it?
Bend it like Ockham
Despite the varying complexity of most (if not all) IoC containers in the field, logically speaking, the code listed above is the absolute minimum amount of code necessary to separate the instantiation of a service instance from its actual client code. When you request a service instance from a given IoC container (whether it be LinFu, Ninject or countless other containers out there), there has to be some point where the container has to decide if that service instance can be created, as well as manage the lifetime of that service once it is already out of the container. As most of us IoC container developers know, there's quite a lot more to a container than just managing the lifetime of its individual services. Creating the instance is only the first step, and I'll show you how LinFu version 2's new IoC container will implement the some of the same features without sacrificing its relatively-horizontal learning curve.
The Pattern of Other Containers
Typically, these containers will use either property setter injection or constructor injection to autowire together all of the dependencies that an application might need during its lifetime. In addition, they might implement additional features such as interception, logging, and AOP. However, despite the differences in features among containers, there are two points where these features are commonly applied:
Now the reason why LinFu's IoC container can get away with such simple code is because it actually delegates its factory methods to an instance of the IFactory interface:
For example, if I wanted to add interception to LinFu.IoC, all I have to do is wrap a Decorator around an IContainer (or INamedContainer) instance that wraps each service instance in a proxy:
...and in the client code, using the additional decorator is as easy as:
As you can see, the implementation of LinFu.IoC is very straightforward, and there's really nothing exotic about the design. For those of you who were probably wondering why LinFu.IoC isn't using generics, here's the list of extension methods that completes the design:
Again, there's nothing unconventional in the design. In the end, it's the simplicity that matters most, and that is the difference that LinFu.IoC offers.
// The SimpleContainer will handle unnamed services
public class SimpleContainer : IContainer
{
private readonly Dictionary_factories = new Dictionary ();
public virtual bool SuppressErrors
{
get; set;
}
public virtual void AddFactory(Type serviceType, IFactory factory)
{
_factories[serviceType] = factory;
}
public virtual bool Contains(Type serviceType)
{
return _factories.ContainsKey(serviceType);
}
public virtual object GetService(Type serviceType)
{
object result = null;
if (!_factories.ContainsKey(serviceType) && !SuppressErrors)
throw new ServiceNotFoundException(serviceType);
if (!_factories.ContainsKey(serviceType) && SuppressErrors)
return null;
// Use the corresponding factory
// and create the service instance
var factory = _factories[serviceType];
if (factory != null)
result = factory.CreateInstance(this);
return result;
}
}
// The named container will handle named services
public class NamedContainer : SimpleContainer, INamedContainer
{
protected readonly Dictionary> _namedFactories =
new Dictionary>();
public virtual void AddFactory(string serviceName, Type serviceType, IFactory factory)
{
if (serviceName == string.Empty)
{
AddFactory(serviceType, factory);
return;
}
// Create the entry, if necessary
if (!_namedFactories.ContainsKey(serviceName))
_namedFactories[serviceName] = new Dictionary();
_namedFactories[serviceName][serviceType] = factory;
}
public virtual bool Contains(string serviceName, Type serviceType)
{
// Use the standard IContainer.Contains(Type)
// if the service name is blank
if (serviceName == string.Empty)
return Contains(serviceType);
return _namedFactories.ContainsKey(serviceName) &&
_namedFactories[serviceName].ContainsKey(serviceType);
}
public virtual object GetService(string serviceName, Type serviceType)
{
// Used the other GetService method if
// the name is blank
if (serviceName == string.Empty)
return GetService(serviceType);
// Determine if the service exists, and
// suppress the errors if necessary
bool exists = Contains(serviceName, serviceType);
if (!exists && SuppressErrors)
return null;
if (!exists && SuppressErrors != true)
throw new NamedServiceNotFoundException(serviceName, serviceType);
var factory = _namedFactories[serviceName][serviceType];
// Make sure that the factory exists
if (factory == null)
return null;
return factory.CreateInstance(this);
}
}
As you can see, there's nothing special about this code. Some might even scoff at it since it's too minimalistic to be useful. After all, one might say that an IoC container has far more responsibilities than just object instantiation. Ninject, for example has features such as contextual binding and method interception. Surely this isn't all there is to LinFu.IoC's features, is it?
Bend it like Ockham
Despite the varying complexity of most (if not all) IoC containers in the field, logically speaking, the code listed above is the absolute minimum amount of code necessary to separate the instantiation of a service instance from its actual client code. When you request a service instance from a given IoC container (whether it be LinFu, Ninject or countless other containers out there), there has to be some point where the container has to decide if that service instance can be created, as well as manage the lifetime of that service once it is already out of the container. As most of us IoC container developers know, there's quite a lot more to a container than just managing the lifetime of its individual services. Creating the instance is only the first step, and I'll show you how LinFu version 2's new IoC container will implement the some of the same features without sacrificing its relatively-horizontal learning curve.
The Pattern of Other Containers
Typically, these containers will use either property setter injection or constructor injection to autowire together all of the dependencies that an application might need during its lifetime. In addition, they might implement additional features such as interception, logging, and AOP. However, despite the differences in features among containers, there are two points where these features are commonly applied:
- When a service is about to be created (e.g. constructor injection) or,
- When a service is already instantiated (e.g. property setter injection, or interception)
Now the reason why LinFu's IoC container can get away with such simple code is because it actually delegates its factory methods to an instance of the IFactory interface:
public interface IFactoryLinFu's IoC container uses each factory instance to determine how a service implementation should be instantiated, and each factory instance, in turn, is responsible for managing the lifetime of each component that it creates. With that in mind, the only thing that you have to do to extend LinFu's IoC container is to control how each factory creates its object instances (such as deciding which constructor and constructor arguments to use when implementing constructor injection) and control the instances that come out of each factory.
{
object CreateInstance(IContainer container);
}
For example, if I wanted to add interception to LinFu.IoC, all I have to do is wrap a Decorator around an IContainer (or INamedContainer) instance that wraps each service instance in a proxy:
public class ContainerDecorator : IContainer
{
private IContainer _container;
public ContainerDecorator(IContainer realContainer)
{
_container = realContainer;
}
// Other methods skipped for brevity
public object GetService(Type serviceType);
{
// Grab the service instance
var result = container.GetService(serviceType);
// Wrap the instance, if possible
if (result != null)
return SomeProxyFactory.Wrap(result);
// Otherwise return the original instance
return result;
}
}
...and in the client code, using the additional decorator is as easy as:
var container = new ContainerDecorator(new SimpleContainer());
// Use the container somehow and transparently use the decorator to wrap the
// result
var service = container.GetService<ISomeServiceType>();
// ...
As you can see, the implementation of LinFu.IoC is very straightforward, and there's really nothing exotic about the design. For those of you who were probably wondering why LinFu.IoC isn't using generics, here's the list of extension methods that completes the design:
public static class ContainerExtensions
{
public static T GetService(this IContainer container)
where T : class
{
var serviceType = typeof (T);
return container.GetService(serviceType) as T;
}
public static T GetService(this INamedContainer container, string serviceName)
where T : class
{
return container.GetService(serviceName, typeof (T)) as T;
}
public static void AddFactory(this INamedContainer container, string serviceName, IFactory factory)
{
IFactory adapter = new FactoryAdapter(factory);
container.AddFactory(serviceName, typeof (T), adapter);
}
public static void AddFactory(this IContainer container, IFactory factory)
{
IFactory adapter = new FactoryAdapter(factory);
container.AddFactory(typeof(T), adapter);
}
public static void AddService(this IContainer container, T instance)
{
container.AddFactory(typeof (T), new InstanceFactory(instance));
}
}
Again, there's nothing unconventional in the design. In the end, it's the simplicity that matters most, and that is the difference that LinFu.IoC offers.
Subscribe to:
Posts (Atom)