Принцип Inversion of Control мне понятен и логичен, но зачем нужен DI я осознать не могу. Пример: public class Samurai { public IWeapon Weapon { get private set } public Samurai(IWeapon weapon) { this.Weapon = weapon } } И тут мы где-то в одном месте программы внедряем зависимость между интерфейсом и конкретной реализацией. Т.е. при необходимости достаточно только тут подменить другую реализацию, например для тестирования или просто так. Здесь используется Ninject, но не суть важно какой фреймворк. this.Bind().To() Окей, все хорошо, НО зачем нам нужен DI фреймворк, если мы можем сделать по сути тоже самое: var samurai = new Samurai(new Sword()) Т.е. в конструктор мы точно так же, как и в примере с DI фреймворком можем подменить реализацию, изменив всего-лишь одно слово в одном месте. Так в чем же тогда смысл DI контейнера? Извиняюсь, если вопрос глупый, я новичок.
Ответ Inversion Of Control - это принцип используемый для уменьшения связанности кода. Dependency Injection - это один из способов реализации данного принципа в котором зависимости передаются через конструктор (как правило) или через установку свойств (реже). Ninject, Unity и т.п. это Dependency Injection Container - инструмент для более простого применения Dependency Injection. Как правило он позволяет более удобно задавать композицию объектов, их время жизни, а так же перехват (interception). Для применения Dependency Injection использование каких-либо фреймворков (контейнеров) не обязательно. Они лишь позволяют сделать это более удобно.
Чтобы было лучше понятно я попробую (совсем вкратце) описать преимущества DI-контейнера. В указанном выше примере все достаточно просто. Существует всего несколько классов которые легко можно вручную скомпоновать вместе. В таком случае смысл в DI-контейнере действительно отсутствует и все можно сделать руками достаточно просто. Но, когда проект становится больше, появляется больше объектов и их вложенность друг в друга становится глубже - управлять всем этим вручную становится достаточно сложно. Потому, что это будет требовать или написания большого объема кода либо собственного, встроенного фреймворка. Контейнер, в свою очередь, позволяет: Задавать различную конфигурацию и удобно ей управлять. Использовать различные соглашения, чтобы контейнер сам "подцеплял" нужные ему типы найденные по определенным условиям в проекте. Если конфигурация читается из внешнего файла (например xml), то поведение программы можно поменять без перекомпиляции. И т.д. и т.п. Так как в сложных случаях может быть так, что объекты которые передаются как зависимости так же могут требовать для себя какие-то зависимости которые так же могут требовать для себя какие зависимости и т.д. реализовывать все это вручную будет трудоемко. Контейнер же с легкостью сделает это все самостоятельно. Позволят задавать время жизни объектов. Рассмотрим простой пример: public class A { public A(IB b, IC c) {} } public interface IB {} public class B { public B(IC c) {} } public interface IC {} public class C {} В данном случае, при создании экземпляра типа А нам требуется экземпляр типа IB и IC, IB в свой очередь требует так же экземлпяр типа IС. Нам может потребоваться или создать в каждом случае новый экземпляр типа IC или исползовать в обоих случаях один и тот же. Контейнер позволяет легко этим манипулировать. Помимо этих двух случаев бывают еще и другие. Позволяет применять перехват (interception). Это возможность с помощью контейнера встраивать какой-нибудь полезный код (типа логирования, проверки прав и т.п.) перед\после вызовов методов интерфейса зависимости. Об этом лучше почитать отдельно, с примерами, для конкретного контейнера. У Ninject для этого есть специальное расширение.