They do not match. Generic types allow you to define functionality that can be applied to a wide range of other types. However, when you create a typical class, the compiler makes a reference to the actual types that were passed as general parameters. Thus, the declaration is static and cannot change after compilation. For example, I can write code that creates an instance of the Node class:
Node<SomeImplementation> node1 = new Node<SomeImplementation>(); Node<SomeOtherImplementation> node2 = new Node<SomeOtherImplementation>();
I am reusing your Node class in different scenarios, but as soon as I compile my assembly, I cannot change the general type of my variables (node1 and node2).
Dependency injection (and IoC containers), on the other hand, allows you to change the functionality of your application at runtime . You can use Dependency Injection to replace one implementation of ISomeInterface
with a completely different implementation at runtime. For example, in the second Node class, I can use the IoC container to create the Node class ... something like:
Node n = Container.Create<Node>();
The IoC container then determines how to instantiate the Node class based on some configuration. He determines that the designer needs an implementation of ISomeInterface
, and he knows how to build an implementation at runtime . I can change my configuration for the IoC container and execute the same build / code, and another implementation of ISomeInterface
will be created and passed to the Node constructor.
This is useful in unit tests because you can mock some parts of your application so that you can test one specific class. For example, you can test some business logic that usually refers to a database. In unit test, you can make fun of the data access logic and introduce new functionality that returns the βstaticβ data needed to validate each particular business case. This breaks the dependence of your tests on the database and allows for more accurate / supported tests.
Edit
As for your update, restricting a constructor without a parameter may not always be desirable. You may have a class (written by you or a third party) that requires parameters. Requiring a class to implement a constructor without parameters can affect application integrity. The idea behind the DI template is that your Node class does not need to know how the class was created.
Suppose you had many levels of classes / dependencies. With typical types, this might look like this:
class MyClass<T> where T : IUtilityClass { ... } class UtilityClass<T> : IUtilityClass where T : IAnotherUtilityClass { ... } class AnotherUtilityClass : IAnotherUtilityClass { ... }
In this case, MyClass uses UtilityClass, and UtilityClass depends on AnotherUtilityClass. Therefore, when you declare MyClass
, you should know each dependency in a row ... not only the MyClass
dependency, but also the UtilityClass
dependency. This expression looks something like this:
MyClass<UtilityClass<AnotherUtilityClass>> myTestClass = new MyClass<UtilityClass<AnotherUtilityClass>>();
This can become cumbersome as you add more and more dependencies. With DI, your caller does not need to know about any nested dependencies, because the IoC container automatically detects them. You just do something like this:
MyClass myTestClass = Container.Create<MyClass>();
You do not need to know anything about the details of MyClass or its utility classes.
There are usually other benefits to IoC containers, for example, many of them provide aspect-oriented programming forms. They also allow you to specify the lifetime of the object, so the object can be single (only one instance will be created, and the same instance will be returned to all callers).