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