Обновление: Мой оригинальный ответ был написан до выпуска .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 Расширение перехвата . Это было бы довольно сложно, и я не думаю, что вы захотите пойти по этому пути, если вам не нужно.