Поддерживает ли Structuremap Lazy из коробки?

Предоставляет ли структурная карта возможность делать инъекцию конструктора ленивым способом? Значение не создает объект, который вводится до его использования?

Ответ 1

UPDATE: StructureMap v3 реализует это из коробки, поэтому этот трюк больше не нужен.


StructureMap версии 2 нет, но с несколькими трюками вы можете заставить его делать то, что, как я считаю, вы ищете. Прежде всего, вы уже можете подключить экземпляры Lazy<T> вручную следующим образом:

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>));
});

Это работает отлично, но вы должны регистрировать каждый тип отдельно. Было бы лучше, если бы вы могли использовать более основанный на конвенциях подход. В идеале, следующий синтаксис будет приятным.

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

Этот синтаксис фактически работает... несколько. К сожалению, во время выполнения StructureMap попытается найти "самый жадный" конструктор для Lazy<T> и опуститься на public Lazy(Func<T> valueFactory, bool isThreadSafe). Поскольку мы не говорили, что делать с логическим параметром isThreadSafe, он будет генерировать исключение, когда он пытается разрешить "Lazy".

В документации для Lazy указано, что "режим безопасности потока" конструктора по умолчанию Lazy(Func<T> valueFactory) равен LazyThreadSafetyMode.ExecutionAndPublication, который, как раз так происходит, является тем, что вы получаете, передавая true в параметр isThreadSafe конструктора выше. Итак, если бы мы могли просто передать StructureMap для передачи true для isThreadSafe, мы получим такое же поведение, как если бы мы назвали конструктор, который мы действительно хотели использовать в первую очередь (например, Lazy(Func<T> valueFactory)).

Простое регистрацию x.For(typeof(bool)).Use(y => true) было бы очень безрассудным и опасным, так как мы будем говорить StructureMap, чтобы продолжить и использовать значение true для любого булева в любом месте. Вместо этого нам нужно указать StructureMap, какое значение использовать только для этого одного логического параметра, который мы можем сделать следующим образом.

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

StructureMap теперь знает, использовать значение "true" для параметра isThreadSafe при разрешении Lazy<T>. Теперь мы можем использовать Lazy<T> в параметрах конструктора и получить поведение, которое, как я полагаю, вы искали.

Вы можете прочитать о классе Lazy более подробно здесь.

Ответ 2

Да, да. Последняя версия StructureMap (2.6.x) скомпилирована против .NET Framework 3.5 и поэтому не имеет доступа к типу Lazy<T>, представленному в .NET 4. Однако он поддерживает ту же функциональность - "не создавая объект который вводится до тех пор, пока он не будет использован". Вместо зависимости от a Lazy<T> вы зависите от a Func<T>. Никакой специальной регистрации контейнера не требуется.

Я включил пример программы, которая создает следующий вывод:

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");
    }
}