Ленивая загрузка с Ninject - PullRequest
13 голосов
/ 29 марта 2010

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

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

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

Ответы [ 2 ]

19 голосов
/ 29 марта 2010

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

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

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


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

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

Вы можете относительно легко выполнить (a), используя интерфейс провайдера привязку метода (ред .: 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, любой библиотекой.


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

17 голосов
/ 11 сентября 2012

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

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