Does Structuremap support Lazy out of the box?
Asked Answered
C

2

10

Does structuremap allow you to do constructor injection in a lazy fashion? Meaning not creating the object which is injected until it is used?

Cornejo answered 25/7, 2011 at 5:2 Comment(5)
Why would an object have a dependency that isn't being used?Brenna
Good question, and valid point. If not all of your methods for the class use the injected object. Some classes could have multiple objects injected, but not each one is used in every method. Maybe that is a smell I suppose?Cornejo
I think a class that doesn't use a dependency is most likely breaking the Single Responsibility Principle. I don't know what your class is doing so I can't be one to judge, but it might be a case where it's trying to do two things and might be time to separate them into different classes. That being said, if it's a one off it might not hurt, objects are cheap to create so I doubt performance is much of an issue at the moment.Brenna
I have to disagree with Phill about the SRP violation in at least one regard. The logic HAS been broken out into multiple classes, that's why they're being injected in. Imagine you have several dependencies, not all of which will be needed right away, and some of which might not get called at all. Building those dependencies would be a waste, so putting it off until the actual call comes through seems like a perfectly valid decision to me.Puisne
I think your statemetn is valid Mel. I do not want my calling assembly to have to know too much, or make a series of "intelligent" calls into, say a biz layer from a UI layer. Some of the decisions encapsulated in the biz layer are grouped more on behavior of an end user and the rules and steps necessary for that behavior to execute successfully may have several dependencies the UI shouldn't have to worry about.Cornejo
P
13

UPDATE: StructureMap v3 implements this out of the box, so this trick is no longer necessary.


StructureMap version 2 doesn't, but with a few tricks you can get it to do what I believe you are looking for. First of all, you can already wire up Lazy<T> instances manually like this:

container = new Container(x =>
{
    x.Scan(y =>
    {
        y.TheCallingAssembly();
        y.WithDefaultConventions();
    });

    x.For<Lazy<IFoo>>().Use(y => new Lazy<IFoo>(y.GetInstance<Foo>));
    x.For<Lazy<IBar>>().Use(y => new Lazy<IBar>(y.GetInstance<Bar>));
    x.For<Lazy<IBaz>>().Use(y => new Lazy<IBaz>(y.GetInstance<Baz>));
});

This works just fine, but you have to register each and every type individually. It would be nicer if you could take advantage of a more convention-based approach. Ideally, the following syntax would be nice.

x.For(typeof(Lazy<>)).Use(typeof(Lazy<>));

This syntax actually works... somewhat. Unfortunately, at runtime, StructureMap will attempt to find the "greediest" constructor for Lazy<T> and settle on public Lazy(Func<T> valueFactory, bool isThreadSafe). Since we didn't tell it what to do with the boolean isThreadSafe parameter, it will throw an exception when it tries to resolve `Lazy'.

The documentation for Lazy states that the "thread safety mode" of the default Lazy(Func<T> valueFactory) constructor is LazyThreadSafetyMode.ExecutionAndPublication, which just so happens to be what you get by passing true into the isThreadSafe parameter of the constructor above. So, if we could just tell StructureMap to pass true for isThreadSafe, we would get the same behavior as if we called the constructor we actually wanted to use in the first place (e.g. Lazy(Func<T> valueFactory)).

Simply registering x.For(typeof(bool)).Use(y => true) would be very reckless and dangerous since we would be telling StructureMap to go ahead and use the value true for any boolean anywhere. Instead, we need to tell StructureMap what value to use for just this one boolean parameter, which we can do like this.

x.For(typeof(Lazy<>)).Use(typeof(Lazy<>))
 .CtorDependency<bool>("isThreadSafe").Is(true);

StructureMap now knows to use the value of "true" for the isThreadSafe parameter when resolving Lazy<T>. We can now use Lazy<T> in constructor parameters, and get the behavior I believe you were looking for.

You can read about the Lazy class in more detail here.

Puisne answered 29/7, 2011 at 20:36 Comment(5)
Note: This is mostly (90%) the same as my answer to question 6814961, but is actually a better fit to this question than that one.Puisne
do you have a link that explains how StructureMap v3 uses lazy loading by default? ThanksPancratium
I wouldn't say "by default". It's just that you don't have to trick it into doing it anymore. In the constructor for your class, instead of taking and IFoo, take Lazy<IFoo> instead. StructureMap won't actually BUILD the Foo until you retrieve it using .Value. I usually take lazy dependencies from the constructor, and store them in a backing variable, then expose them through a property like "private IFoo Foo { get { return _foo.Value; }}"Puisne
Can you show how Lazy is now available out of the box?Taxaceous
I mean that you don't have to tell StructureMap what to do with lazy dependencies anymore. In the constructor for your service/controller/whatever, just type the dependencies as Lazy<IThingService> instead of simply IThingService. StructureMap should hold off on creating the instance until you actually use it.Puisne
U
5

Yes, it does. The latest version of StructureMap (2.6.x) is compiled against .NET Framework 3.5, and so does not have access to the Lazy<T> type introduced in .NET 4. However, it does support the same functionality - "not creating the object which is injected until it is used". Instead of depending on a Lazy<T>, you depend on a Func<T>. No special container registration is required.

I've included a sample program that creates the following output:

Created Consumer
Consuming
Created Helper
Helping

Sample.cs:

class Program
{
    static void Main(string[] args)
    {
        var container = new Container(x =>
        {
            x.For<IConsumer>().Use<Consumer>();
            x.For<IHelper>().Use<Helper>();
        });

        var consumer = container.GetInstance<IConsumer>();
        consumer.Consume();
    }
}

public class Consumer : IConsumer
{
    private readonly Func<IHelper> _helper;

    public Consumer(Func<IHelper> helper)
    {
        _helper = helper;
        Console.WriteLine("Created Consumer");
    }

    public void Consume()
    {
        Console.WriteLine("Consuming");
        _helper().Help();
    }
}

public interface IConsumer
{
    void Consume();
}

public interface IHelper
{
    void Help();
}

public class Helper : IHelper
{
    public Helper()
    {
        Console.WriteLine("Created Helper");
    }

    public void Help()
    {
        Console.WriteLine("Helping");
    }
}
Unnecessarily answered 7/8, 2011 at 14:10 Comment(2)
I would say that StructureMap supports lazy (Little "l"), but not Lazy (Big "L"). Maybe I read too much into the capitalization in the question, but I took it to be significant. I've also used the Func<> trick. It accomplishes the same results, but requires you to add parentheses to each and every usage, which should indicate that the Func will be executed each time it is used. Alternately, you can add an additonal property in front of the backing field for the Func, and do something like "return _helper ?? (_helper = _helperFunc());Puisne
The intent of the question seemed to be the second sentence. The user wants to delay when objects are instantiated. That is what I answered.Unnecessarily

© 2022 - 2024 — McMap. All rights reserved.