Lazy Загрузка с помощью Ninject

Я оцениваю ninject2, но не могу понять, как делать ленивую загрузку, кроме как через ядро.

Из того, что я вижу, такого рода поражения - цель использования атрибутов [Inject]. Можно ли использовать InjectAttribute, но получить ленивую загрузку? Мне не хотелось бы принудительно завершать построение графа объектов каждый раз, когда я создавал экземпляр объекта.

Чтобы указать, мне действительно интересно узнать о производительности.

Ответы

Ответ 1

Обновление: Мой первоначальный ответ был написан до того, как была выпущена .NET Framework 4 (вместе с Lazy<T>), а другой ответ, немного более современный, по-прежнему является бит теперь устарел. Я оставляю свой первоначальный ответ ниже, если кто-то застрял на более старой версии, но не рекомендовал бы его использовать с последней версией Ninject или .NET.

Ninject Factory Расширение - это современный способ сделать это. Он автоматически подключит любые аргументы или свойства. Для этого вам не требуется отдельная привязка - просто настройте свои сервисы обычным способом, а расширение обрабатывает остальные.

FYI, такое же расширение может также подключаться к пользовательским интерфейсам factory или Func<T>. Разница заключается в том, что каждый раз они будут создавать новый экземпляр, поэтому на самом деле это factory, а не просто ленивый экземпляр. Просто указывая это, потому что документация не совсем понятна.


Как правило, "полное построение графа объектов" не должно быть настолько дорогостоящим, и если класс вводится с зависимостями, которые он не может использовать, это, вероятно, хороший признак того, что у класса слишком много обязанностей.

На самом деле это не ограничение Ninject - если вы думаете об этом, на самом деле не может быть "ленивой зависимости", если только (а) вложенная зависимость сама по себе является ленивым загрузчиком, например, Lazy<T> класс в .NET 4 или (b) все свойства и методы зависимостей используют ленивую инстанцирование. Что-то нужно вводить там.

Вы можете выполнить (а) относительно легко, используя интерфейс поставщика привязку метода (ed: Ninject не поддерживает открытые дженерики с привязками к поставщику) и привязывает открытый общий тип. Предполагая, что у вас нет .NET 4, вам нужно будет создать интерфейс и реализацию самостоятельно:

public interface ILazy<T>
{
    T Value { get; }
}

public class LazyLoader<T> : ILazy<T>
{
    private bool isLoaded = false;
    private T instance;
    private Func<T> loader;

    public LazyLoader(Func<T> loader)
    {
        if (loader == null)
            throw new ArgumentNullException("loader");
        this.loader = loader;
    }

    public T Value
    {
        get
        {
            if (!isLoaded)
            {
                instance = loader();
                isLoaded = true;
            }
            return instance;
        }
    }
}

Затем вы можете привязать весь ленивый интерфейс - просто привяжите интерфейсы как обычно:

Bind<ISomeService>().To<SomeService>();
Bind<IOtherService>().To<OtherService>();

И привяжите ленивый интерфейс с помощью открытых генераторов к методу лямбда:

Bind(typeof(ILazy<>)).ToMethod(ctx =>
{
    var targetType = typeof(LazyLoader<>).MakeGenericType(ctx.GenericArguments);
    return ctx.Kernel.Get(targetType);
});

После этого вы можете ввести ленивые аргументы/свойства зависимостей:

public class MyClass
{
    [Inject]
    public MyClass(ILazy<ISomeService> lazyService) { ... }
}

Это не сделает всю операцию ленивой - Ninject все равно должен будет создать экземпляр LazyLoader, но любые зависимости второго уровня ISomeService не будут загружены до тех пор, пока MyClass не проверит Value этого lazyService.

Очевидным недостатком является то, что ILazy<T> не реализует сам T, поэтому MyClass должен быть фактически написан для принятия ленивых зависимостей, если вы хотите воспользоваться ленивой загрузкой. Тем не менее, если это чрезвычайно дорого для создания определенной зависимости, это будет хорошим вариантом. Я уверен, что у вас будет эта проблема с любой формой DI, любой библиотеки.


Насколько я знаю, единственный способ сделать (б) без написания гор кода - использовать генератор прокси, например Castle DynamicProxy, или зарегистрируйте динамический перехватчик, используя Расширение перехвата Ninject. Это было бы довольно сложно, и я не думаю, что вы хотите пойти по этому маршруту, если вам не нужно.

Ответ 2

Существует более простой способ сделать это:

public class Module : NinjectModule
{
    public override void Load()
    {
        Bind(typeof(Lazy<>)).ToMethod(ctx => 
                GetType()
                    .GetMethod("GetLazyProvider", BindingFlags.Instance | BindingFlags.NonPublic)
                    .MakeGenericMethod(ctx.GenericArguments[0])
                    .Invoke(this, new object[] { ctx.Kernel }));
    }

    protected Lazy<T> GetLazyProvider<T>(IKernel kernel)
    {
        return new Lazy<T>(() => kernel.Get<T>());
    }
}