have been evaluating Inversion of Control (IoC) containers and offerings lately to determine a good fit for a new project in Wpf. I have looked at Unity, StructureMap, and Windsor (although only really a glance at Windsor). I've read a few articles as well comparing a few and there seems to be a consensus with like minded people like myself (fans of DSL style command-chaining and Lambda) in favour of StructureMap. Another reason to like StructureMap is the evident effort to keep pace with language changes and constant improvement in finding more concise ways to implement the IoC pattern.
Here's a good definition of IoC: http://stackoverflow.com/questions/3058/what-is-inversion-of-control
Terminology:
I use the term factory to abbreviate IoC Container.
The only thing left to decide is how to configure it and which injection strategy to use.
[Edit 10-June-2010]
Configuration
Options are:
Configuration by app.config, xml, or code.
This seems to be an easy answer in my opinion. If something needs to be customisable per user / install then that registration must go into the app.config / xml, otherwise code registration is much faster. Code registration also ensures everyone on the team gets new registration entries as my team does not share app.config files (developer preferences interfere).
1 <?xml version="1.0" encoding="utf-8" ?>
2 <configuration>
3 <configSections>
4 <section name="StructureMap" type="StructureMap.Configuration.StructureMapConfigurationSection,StructureMap"/>
5 </configSections>
6
7 <StructureMap>
8 <DefaultInstance
9 PluginType="Framework.Logging.ILogWriter,Framework"
10 PluggedType="Framework.Logging.Log4NetLogWriter,Framework"
11 />
12 <DefaultInstance
13 PluginType="StructureMap.Testing.Widget.IRule,StructureMap.Testing.Widget"
14 PluggedType="StructureMap.Testing.Widget.ColorRule,StructureMap.Testing.Widget"
15 Color="Blue"
16 Scope="Singleton"
17 Name="TheBlueOne"/>
18 </StructureMap>
19 </configuration>
6 var existingInstance = new Widget();
7 ObjectFactory.Initialize(init => {
8 init.For<ILogWriter>().Use<FakeLogWriter>();
9 init.For<IService>().Singleton().Use<MyService>();
10 init.For<IWidget>().Use(existingInstance);
11 init.For<IComplex>().Use(() => new ComplexToBuildObject());
12 });
Usage Strategies:
This where it gets more complicated, and probably purely personal preference. Options are:
- Constructor Injection
- Setter Injection
- Lambda / Function builders
Constructor Injection
The most common usage is Constructor Injection. This involves injecting dependencies into a public constructor at object creation time.
42 public class TestConstructor {
43 public TestConstructor(ITestSingleton singleton, ITestTransient transient) {
44 this.SingletonProperty = singleton;
45 this.TransientProperty = transient;
46 }
47
48 public ITestTransient TransientProperty { get; private set; }
49
50 public ITestSingleton SingletonProperty { get; private set; }
51 }
18 [TestFixture]
19 public class TestConstructorInjection {
20 [Test]
21 public void ConstructorInjection() {
22 var factory = new Container(config => {
23 config.For<ITestSingleton>().Singleton().Use<TestSingleton>();
24 config.For<ITestTransient>().Use<TestTransient>();
25 });
26
27 var target = factory.GetInstance<TestConstructor>();
28
29 Assert.IsNotNull(target.TransientProperty);
30 Assert.IsInstanceOf<TestTransient>(target.TransientProperty);
31 Assert.IsNotNull(target.SingletonProperty);
32 Assert.IsInstanceOf<TestSingleton>(target.SingletonProperty);
33 }
34 }
Pros / Cons:
+ Not bad speed performance (see further on in this post)
+ Small amount of registration code (1 per dependency globally - ie if multiple classes use ITestSingleton no more registrations are required).
- Requires public constructor even if you do not want the class createable externally.
Setter Injection:
This is less common, but may be more desirable now with C# Object Initialisers favoured over constructors to simply set properties.
18 public class ClassWithPropertyDependencies {
19 [SetterProperty]
20 public ITestTransient TransientProperty { get; set; }
21
22 [SetterProperty]
23 public ITestSingleton SingletonProperty { get; set; }
24 }
...
26 [TestFixture]
27 public class TestSetterInjection {
28 [Test]
29 public void SetterInjection() {
30 var factory = new Container(init => {
31 init.For<ITestTransient>().Use<TestTransient>();
32 init.SetAllProperties(setter => setter.OfType<ITestTransient>());
33
34 init.For<ITestSingleton>().Singleton().Use<TestSingleton>();
35 init.SetAllProperties(setter => setter.OfType<ITestSingleton>());
36 });
37
38 var target = factory.GetInstance<ClassWithPropertyDependencies>();
39 var singleton = factory.GetInstance<ITestSingleton>();
40
41 Assert.IsNotNull(target.TransientProperty);
42 Assert.IsInstanceOf<TestTransient>(target.TransientProperty);
43 Assert.IsNotNull(target.SingletonProperty);
44 Assert.IsInstanceOf<TestSingleton>(target.SingletonProperty);
45 Assert.AreSame(singleton, target.SingletonProperty);
46 }
}
Pros / Cons:
- Requires a public constructor still for this to work with StructureMap. Error 202 is thrown if the TestMaster constructor is made internal
- Requires properties that need to be set to be public settable. If they are not they will not be populated. This pollutes the design and can lead to misuse of a class.
- Slowest for of initialisation.
+ Good level of configuration (2 lines per dependency that needs to be used globally).
Lambda / Function Builder:
This registers a function or lambda to create an object instead of simply a type (a builder pattern).
18 public class LambdaBuilderMaster {
19 public static LambdaBuilderMaster CreateInstance() {
20 return new LambdaBuilderMaster {
21 TransientProperty = new TestTransient(),
22 SingletonProperty = new TestSingleton(),
23 };
24 }
25
26 public ITestTransient TransientProperty { get; private set; }
27
28 public ITestSingleton SingletonProperty { get; private set; }
29 }
...
39 [TestFixture]
40 public class TestBuilderStrategy {
41 [Test]
42 public void LambdaOrFunctionBuilder() {
43 var factory = new Container(init => init.For<LambdaBuilderMaster>().Use(LambdaBuilderMaster.CreateInstance));
44
45 var target = factory.GetInstance<LambdaBuilderMaster>();
46 var singleton = factory.GetInstance<ITestSingleton>();
47
48 Assert.IsNotNull(target.TransientProperty);
49 Assert.IsInstanceOf<TestTransient>(target.TransientProperty);
50 Assert.IsNotNull(target.SingletonProperty);
51 Assert.IsInstanceOf<TestSingleton>(target.SingletonProperty);
52 Assert.AreSame(singleton, target.SingletonProperty);
53 }
}
Pros / Cons:
+ Does not require a public constructor.
+ Does not require inappropriate properties to be settable.
+ Best performance.
- Requires a great deal of registration (1 for every type used with the IoC factory).
- Build process adds complexity to code.
Performance:
Here is a test that compares different creation methods, not so surprising results.
44 [Test]
45 public void Performance() {
46 const int CreateThisMany = 10000;
47 long time;
48 var results = new List<KeyValuePair<string, long>>(3);
49
50 // StructureMap Setter Injection - requires a lot of configuration. 2 registrations for every property type that needs to be auto-filled.
51 time = FactoryPerformanceTest(
52 new Container(config => {
53 config.For<ITestSingleton>().Use<TestSingleton>();
54 config.For<ITestTransient>().Use<TestTransient>();
55 config.SetAllProperties(setter => setter.OfType<ITestSingleton>());
56 config.SetAllProperties(setter => setter.OfType<ITestTransient>());
57 }),
58 CreateThisMany,
59 factory => factory.GetInstance<ClassWithPropertyDependencies>());
60 results.Add(new KeyValuePair<string, long>("Setter Injection", time));
61
62 // Control speed benchmark using "new"
63 time = FactoryPerformanceTest(null, CreateThisMany, factory => new ClassWithPropertyDependencies {
64 TransientProperty = new TestTransient(),
65 SingletonProperty = new TestSingleton(),
66 });
67 results.Add(new KeyValuePair<string, long>("New Operator", time));
68
69 // StructureMap Constructor Injection - requires no config to auto populate constructor arguments. Only 1 registration is needed per argument type globally.
70 time = FactoryPerformanceTest(
71 new Container(config => {
72 config.For<ITestSingleton>().Use<TestSingleton>();
73 config.For<ITestTransient>().Use<TestTransient>();
74 }),
75 CreateThisMany,
76 factory => factory.GetInstance<TestConstructor>());
77 results.Add(new KeyValuePair<string, long>("Constructor Injection", time));
78
79 // StructureMap using builder functions/lambdas - will require a registration for every type that has dependencies - probably the most configuration.
80 var builderFactory = new Container(
81 config => {
82 config.For<ITestSingleton>().Use<TestSingleton>();
83 config.For<ITestTransient>().Use<TestTransient>();
84 });
85 builderFactory.Configure(
86 config => config.For<TestConstructor>().Use(
87 () => new TestConstructor(builderFactory.GetInstance<ITestSingleton>(), builderFactory.GetInstance<ITestTransient>())
88 ));
89 time = FactoryPerformanceTest(
90 builderFactory,
91 CreateThisMany,
92 factory => factory.GetInstance<TestConstructor>());
93 results.Add(new KeyValuePair<string, long>("Function/Lambda builders", time));
94
95 // AND THE WINNER IS...
96 results.ForEach(result => System.Diagnostics.Debug.WriteLine("{0} = {1}ms", result.Key, result.Value));
97 }
And my output:
***** IoCComparison.StructureMapPerformanceTest.StructureMapPerformance
Setter Injection = 200ms
New Operator = 1ms
Constructor Injection = 69ms
Function/Lambda builders = 20ms
***** IoCComparison.NinjectPerformanceTest.NinjectPerformance
Setter Injection = 594ms
New Operator = 2ms
Constructor Injection = 516ms
Function/Lambda builders = 521ms
Structure Map versus Ninject
Its clear to see based on the performance results above that the same usage scenarios through both frameworks that StructureMap is the clear winner. The only downside I can see to StructureMap is that there is no support for Silverlight (Client Framework)... yet. A recent post on Stack Overflow points to July/August 2010 for a Silverlight release of StructureMap. I do however really like Ninject's mission statement to keep their framework simple. In terms of quality of documentation and widespread adoption, again, StructureMap seems to be the clear winner.
- If your class is intended to be public createable then use Constructor Injection. This is less configuration and it exhibits auto-detect dependencies capability; meaning if someone adds a new parameter to the constructor, the factory may be able to populate it without a new registration.
- If a class is not intended to be public createable then use the Lambda / Function Builder strategy. In addition it may sometimes be cleaner code to use this over constructor injection. Having said that, if you have a constructor with many arguments it could indicate the class is in need of a refactor.
- Only in special cases use Setter Injection where there are very strong reasons that the above two methods are not appropriate. That means more than "I don't like the above two".
Service Locator Pattern (anti-pattern?)
IoC factories are quite often used as a Service Locator pattern. This means a singleton instance is available globally throughout the application via a static class and property. For example:
9 public static class ObjectFactory {
10 public static IContainer Container { get; }
11 }
This isn't overly bad until you consider if your code is sprinkled with this call all over the place, creating a tight dependency on a particular IoC and one instance of it.
19 var service = ObjectFactory.Container.GetInstance<IService>();
On the up side it minimises all other coupling quite nicely. So instead of having tight coupling with all components you only have tight coupling with one.
One major downside is unit testing. Consider if you have hundreds of tests, with some running simultaneously, each trying to configure and use the static ObjectContainer all at once... not going to work well.
There is no easy way to completely eliminate these service locator calls. The key thing is to minimise the use of a static ServiceLocator call.
My simple strategy is to use a protected or private property of type IContainer.
23 public class Service : IService {
24 public Service() : base() {
25 }
26
27 private IContainer factory = null;
28 protected IContainer Factory {
29 get {
30 if (this.factory == null) {
31 this.factory = ObjectFactory.Container;
32 }
33
34 return this.factory;
35 }
36
37 set {
38 this.factory = value;
39 }
40 }
41 }
To test the above example class, the test can instantiate it and then set the Factory property using a private accessor strategy (I use reflection to set these in tests, MsTest has a great private accessor feature).
70 [Test]
71 public void TestService() {
72 var target = new Service();
73 var testFactory = new StructureMap.Container();
74 PrivateAccessor.SetProperty(target, "Factory", testFactory);
75 var result = target.DoSomething();
76 // ... Test outcome etc
77 }
There are a few ways to set private properties, lets not go into that now, suffice to say, my PrivateAccessor is simply using reflection to get the property and set it. But the above test works fine and will not compete with other tests for a shared static resource.
Production code does not need to concern itself with any special treatment of the class, and this is as it should be; test code should not pollute classes. The only caveat is that you cannot use the Factory property inside the constructor (this can be avoided with lazy loading of properties the same way the Factory property works).
...
78 var service = new Service();
79 var data = service.DoSomething();
...
Hope that helps someone.
Cheers
Here's a great post on returning conditional instances.
ReplyDeletehttp://codebetter.com/jeremymiller/2009/01/19/conditional-object-construction-in-structuremap-i-e-fun-with-lambdas/