Introduction
The intention of this post is to explain the constructor injection principal, and in particular the singleton approach (which is very static in its approach, but has a place in a design pattern – for e.g. a logger class, which doesn’t need to be re-instantiated in each class that uses it).
What is Dependency Injection (A Recap)
Dependency injection is also known as Inversion of Control (IOC). It’s a design pattern that removes the tight coupling between dependent components. It facilitates the design and implementation of loosely coupled, reusable, and testable objects in your software.
When Should I Use Unity Dependency Injection?
Dependency injection provides opportunities to simplify code, abstract dependencies between objects, and automatically generate dependent object instances. In general, you should use Unity when:
- Your objects and classes may have dependencies on other objects or classes.
- Your dependencies require abstraction.
- You want to take advantage of constructor injection features.
- You want to manage the lifetime of object instances.
- You want to intercept calls to methods or properties to generate a policy chain or pipeline containing handlers that implement cross-cutting tasks.
Benefits of Unity
Unity provides developers with the following advantages:
- It provides simplified object creation, especially for hierarchical object structures and dependencies, which simplifies application code. It contains a mechanism for building (or assembling) instances of objects, which may contain other dependent object instances.
- It supports abstraction of requirements; this allows developers to specify dependencies at run time or in configuration and simplify the management of cross-cutting concerns.
- It increases flexibility by deferring component configuration to the container. It also supports a hierarchy for containers.
- It has a service location capability, which is useful in many scenarios where an application makes repeated use of components that decouple and centralize functionality.
- It allows clients to store or cache the container. This is especially useful in ASP.NET Web applications where developers can persist the container in the ASP.NET session or application.
- It has an interception capability, which allows developers to add functionality to existing components by creating and using handlers that are executed before a method or property call reaches the target component, and again as the calls returns.
- It can read configuration information from standard configuration systems, such as XML files, and use it to configure the container.
- It makes no demands on the object class definition. There is no requirement to apply attributes to classes (except when using property or method call injection), and there are no limitations on the class declaration.
- It supports custom container extensions that you can implement; for example, you can implement methods to allow additional object construction and container features, such as caching.
- It allows architects and developers to more easily implement common design patterns often found in modern applications.
The Types of Objects Unity Can Create
You can use the Unity container to generate instances of any object that has a public
constructor (in other words, objects that you can create using the new
operator), without registering a mapping for that type with the container. When you call the Resolve
method and specify the default instance of a type that is not registered, the container simply calls the constructor for that type and returns the result.
Managing the Lifetime of Objects
Unity allows you to choose the lifetime of objects that it creates. By default, Unity creates a new instance of a type each time you resolve that type. However, you can use a lifetime manager to specify a different lifetime for resolved instances. For example, you can specify that Unity should maintain only a single instance (effectively, a singleton). It will create a new instance only if there is no existing instance. If there is an existing instance, it will return a reference to this instead. Also, you can use a lifetime manager that holds only a weak reference to objects so that the creating process can dispose them, or a lifetime manager that maintains a separate single instance of an object on each separate thread that resolves it.
Code Explanation
Registering the Interfaces with Unity
In the line below, we create an instance of the UnityContainer
.
myContainer = new UnityContainer();
The first registration type (snippet below) we create is the ‘Employee
’ class This class takes the ‘Person
’ class as a constructor parameter. We are giving the registration a name ‘Person-Non-Singleton
’, which we can later to reference this specific ‘Employee
’ class. The ‘Person
’ class in this instance will be created anew each time (not a singleton lifetime).
myContainer.RegisterType<IEmployee, Employee>("Person-Non-Singleton");
myContainer.Resolve<IEmployee>("Person-Non-Singleton");
The next registration, we register the ‘Employee
’ class to take a mapped parameter (in this case the mapping name is ‘Person-Singleton
’) of a singleton ‘Person
’ class. The mapping names are used in our code to reference a particular registered type, as we can have the ‘Employee
’ class generated in a number of different ways.
myContainer.RegisterType<IEmployee, Employee>("EmployeeWithPersonSingleton",
new InjectionConstructor(new ResolvedParameter<Person>("Person-Singleton")));
myContainer.Resolve<IEmployee>("EmployeeWithPersonSingleton");
The following code shows the registration of the ‘Person
’ class that will be used in the default ‘Employee
’ creation. In that, the ‘Person
’ class is not a singleton and doesn’t hold any values that may have been assigned to a previous singleton lifetime ‘Person
’ class.
myContainer.RegisterType<IPerson, Person>("Person-Non-Singleton");
myContainer.Resolve<IPerson>("Person-Non-Singleton");
And finally, a registration of the ‘Person
’ class but as a singleton lifetime instance, thus when it is referenced again it will hold any previous values that were assigned to it previously.
myContainer.RegisterType<IPerson, Person>
("Person-Singleton", new ExternallyControlledLifetimeManager());
myContainer.Resolve<IPerson>("Person-Singleton");
try
{
myContainer = new UnityContainer();
myContainer.RegisterType<IEmployee, Employee>("Person-Non-Singleton");
myContainer.Resolve<IEmployee>("Person-Non-Singleton");
myContainer.RegisterType<IEmployee, Employee>
("EmployeeWithPersonSingleton", new InjectionConstructor
(new ResolvedParameter<Person>("Person-Singleton")));
myContainer.Resolve<IEmployee>("EmployeeWithPersonSingleton");
myContainer.RegisterType<IPerson, Person>
("Person-Singleton", new ExternallyControlledLifetimeManager());
myContainer.Resolve<IPerson>("Person-Singleton");
myContainer.RegisterType<IPerson, Person>("Person-Non-Singleton");
myContainer.Resolve<IPerson>("Person-Non-Singleton");
outMessage = "Populated Unity Container from the Application object.\n";
}
catch (Exception ex)
{
outMessage = "Error: Unity Container not populated correctly.
\n Details: " + ex.Message + "\n";
}
Click Events
private void btnNewEmployee_Click(object sender, RoutedEventArgs e){
IEmployee myEmployeeInstance = myContainer.Resolve<IEmployee>
("Person-Non-Singleton");
this.txtblkOutput.Text += myEmployeeInstance.myPerson.Print();
}
When the user clicks the button ‘New Employee Class w\non-singleton Person Class’, a new employee class is created with a non-singleton (mapping) ‘Person
’ class. If you print the age of the person it will always be 0
, as it will be a newly instantiated ‘Person
’ class.
The following snippet of code will print the ‘Person
’ age property (which if not updated will be 0
), but if you update the value and then click this button again, it will then display the updated person’s age (even though we have created a new instance of the ‘Employee
’ class – weak coupling.
private void btnNewEmployeeSingleton_Click(object sender, RoutedEventArgs e)
{
IEmployee myEmployeeInstance =
myContainer.Resolve<IEmployee>("EmployeeWithPersonSingleton");
this.txtblkOutput.Text += myEmployeeInstance.myPerson.Print();
}
This method will do the same as the above method, but it is just to prove the point that a new instance of ‘Employee
’ is created, but with a singleton lifetime manager for the ‘Person
’ class (thus you will see the updated age property being displayed).
private void btnCurrentEmployeeSingleton_Click(object sender, RoutedEventArgs e)
{
IEmployee myEmployeeInstance = myContainer.Resolve<IEmployee>
("EmployeeWithPersonSingleton");
this.txtblkOutput.Text +=
myEmployeeInstance.myPerson.Print();
}
Finally, the update
method will change the the age
property of the ‘Person
’ class. When it is created with a singleton existance and later referenced with a singleton instance, you will be able to view the updated value, even though the initial instance of the ‘Person
’ class does not exist or is out of scope.
private void btnUpdate_Click(object sender, RoutedEventArgs e)
{
IEmployee myEmployeeInstance = myContainer.Resolve<IEmployee>
("EmployeeWithPersonSingleton");
try
{
myEmployeeInstance.myPerson.Age = Convert.ToInt32(this.txtNewValue.Text);
}
catch (Exception)
{
this.txtblkOutput.Text += "Please enter an integer.";
}
}
Classes
The ‘Employee
’ class is the class that is always instantiated in our application, but by using constructor injection approach, we are able to create the ‘Person
’ class.
using NotificationLibrary.Interfaces;
namespace NotificationLibrary.Model
{
public class Employee : NotificationLibrary.Interfaces.IEmployee
{
public IPerson myPerson { set; get; }
public Employee(Person baseObject)
{
myPerson = baseObject;
}
public string Print() { return "Hello from an employee.\n"; }
}
}
The person
class has a property called ‘Age
’, that we can update and then later we can view this property later in the application by clicking on the other singleton buttons to view the previously saved age
value.
namespace NotificationLibrary.Model
{
public class Person : NotificationLibrary.Interfaces.IPerson
{
private int age;
public int Age
{
get { return this.age; }
set { this.age = value; }
}
public Person() { this.Age = 0; }
public string Print() { return "Hello from a person that is
constructor injected into this onject. My age is " + this.Age.ToString() + "\n"; }
}
}
Interfaces
using System;
using NotificationLibrary.Model;
namespace NotificationLibrary.Interfaces
{
public interface IEmployee
{
IPerson myPerson { set; get; }
string Print();
}
}
using System;
namespace NotificationLibrary.Interfaces
{
public interface IPerson
{
string Print();
int Age { set; get; }
}
}
Screenshot