This had me stumped for a long time a few days back, so I thought I’d post it in case it trips up anyone else as well. This gotcha applies when asking StructureMap (v 2.5+) to resolve a type which needs an array or other IEnumerable<T>
injected.
Much ado about somethings
Say I have the following code:
public class Something { } public class SomeClass { public IEnumerable<Something> Somethings { get; private set; } public SomeClass(IEnumerable<Something> somethings) { Somethings = somethings; } }
I would like to ask StructureMap, our friendly neighbourhood IoC container, to resolve an instance of SomeClass
for me, injecting a specific IEnumerable<Something>
instance into the constructor call. Piece of cake!
public class Bootstrapper { public static readonly IEnumerable<Something> ThreeSomethings = new[] { new Something(), new Something(), new Something() }; public static void Start() { ObjectFactory.Initialize(x => { x.For<IEnumerable<Something>>().Use(ThreeSomethings); }); } }
Let’s check our ObjectFactory
is resolving our IEnumerable
correctly:
[Test] public void ShouldResolveEnumerableOfThreeSomethings() { Bootstrapper.Start(); var somethings = ObjectFactory.GetInstance<IEnumerable<Something>>(); Assert.That(somethings, Is.EquivalentTo(Bootstrapper.ThreeSomethings)); }
It passes. Too easy! Now let’s get our SomeClass
instance and start work!
[Test] public void ShouldResolveSomeClassWithThreeSomethings() { Bootstrapper.Start(); var someClass = ObjectFactory.GetInstance<SomeClass>(); Assert.That(someClass.Somethings, Is.EquivalentTo(Bootstrapper.ThreeSomethings)); }
Er, that test fails. According to NUnit, it expected 3 somethings, but it got an empty enumerable. But hang on, we already tested we had registered and can resolve our IEnumerable<Something>
, and it’s still passing! So what’s going on?
Auto-wiring of IEnumerable<T>
types in StructureMap
As of version 2.5 and later, StructureMap has special handling for injecting IEnumerable<T>
types including arrays, lists of T, and IEnumerable<Something>
. Rather than looking for a specific, registered instance of the enumerable itself, it will just inject all registered instances of type T.
In our case we have registered an IEnumerable<Something>
, but no Something
instances. StructureMap’s special handling for the enumerable type injects all our registered instances, i.e. an empty enumerator. We can fix it by changing our ObjectFactory.Initialize
expression to explicitly add each instance:
ObjectFactory.Initialize(x => { x.For<IEnumerable<Something>>().Use(ThreeSomethings); foreach (var something in ThreeSomethings) { x.For<Something>().Add(something); } });
This passes both tests, although we probably don’t care so much about directly resolving an IEnumerable<Something>
anymore, so we could take out the x.For<IEnumerable<Something>>().Use(ThreeSomethings);
line from the initialisation block and remove that test.
Workarounds (ssshh, don’t do this!)
If you really (really!) don’t want to use this officially sanctioned method of injecting enumerables, you can override the behaviour by telling StructureMap what to use for that specific constructor argument:
x.ForConcreteType<SomeClass>() .Configure .Ctor<IEnumerable<Something>>() .Is(ThreeSomethings);
Or better yet, wrap your enumerable in a new class or interface with more relevance to the domain instead of the built-in framework type. If SomeClass
took a Somethings
class instead of an IEnumerable<Something>
then StructureMap would wire it up in the standard way.
Hope this saves someone the couple of hours of hair-tearing it cost me. :)