Unity is a lightweight and extensible dependency injection (DI) container developed by Microsoft's Patterns & Practices team. It supports three common DI modes: Constructor Injection, Property Injection, and Method Call Injection. The latest version, 1.2, can be downloaded from the Microsoft open-source site at Unity Codeplex. By using Unity, we can easily build loosely coupled applications, making the overall framework clear and maintainable.
During everyday coding, program logic is often complex, especially in large projects where a module frequently references other modules. Suppose we have a Monitor class that monitors CPU temperature. When the temperature reaches a warning threshold, the monitor has an Alarm method that uses an SMS notifier to send a message to the maintenance staff. This results in the most common code like the following:
public class Monitor
{
public void Alarm()
{
SMSNotify notify = new SMSNotify();
notify.Send();
}
}
In the Monitor class, a direct reference to an SMS notifier class is the least flexible and least extensible approach. We might think of interface-oriented programming and polymorphism to provide flexible implementations of different subclasses and improve code extensibility. However, the interface still needs an implementation, meaning the following statement will eventually execute:
public void Alarm()
{
INotify notify = new SMSNotify();
notify.Send();
}
Even when implementing the INotify interface, a concrete class is still required, and such code is fixed at compile time. If a new notifier is needed later, the source code must be modified and recompiled. Moreover, the Monitor class explicitly depends on the SMSNotify class, creating tight coupling. Therefore, the Inversion of Control (IoC) pattern is proposed to solve this problem, deferring the concrete implementation of interfaces until runtime. This way, even with a new implementation class, the caller's code does not need to be changed (can be achieved using configuration files in Unity). The IoC pattern can be metaphorically described as: an interface is like an empty shell, and during concrete implementation, content is injected into this shell to make it a real entity. This pattern is also known as Dependency Injection. By using Unity, we can build loosely coupled software without worrying about the details of object relationships, which can be fully managed by the DI container.
As mentioned earlier, there are three common forms of DI: Constructor Injection, Property Injection, and Method Call Injection. We can implement these three forms with the following example, using the scenario above.
1. Constructor Injection
Define the IMonitor interface:
public interface IMonitor
{
void Alarm();
}
The Monitor class:
public class Monitor : IMonitor
{
private INotify _notify;
public Monitor(INotify notify)
{
_notify = notify;
}
public void Alarm()
{
_notify.Send();
}
}
Define the INotify interface:
public interface INotify
{
void Send();
}
The EmailNotify class:
public class EmailNotify : INotify
{
public void Send()
{
Console.WriteLine("Sending email notification...");
}
}
The SMSNotify class:
public class SMSNotify : INotify
{
public void Send()
{
Console.WriteLine("Sending SMS notification...");
}
}
As you can see, the constructor of Monitor accepts an INotify parameter. The Alarm method calls Send() on the injected implementation, but which implementation's Send is called is only known after injection. In the Unity container, RegisterType and Resolve methods are typically used to register and obtain instances. These methods have many generic and non-generic overloads; refer to Unity's official documentation for specific types and parameters.
Now inject an INotify instance into Monitor's constructor:
static void Main(string[] args)
{
IUnityContainer container = new UnityContainer();
container.RegisterType<IMonitor, Monitor>()
.RegisterType<INotify, SMSNotify>();
IMonitor monitor = container.Resolve<IMonitor>();
monitor.Alarm();
Console.ReadLine();
}
The code injects an SMSNotify instance as INotify. Calling monitor.Alarm() internally calls notify.Send().
If there are multiple constructors, specify which constructor should be injected by adding the [InjectionConstructor] attribute:
public Monitor(INotify notify, string name)
{
_notify = notify;
}
[InjectionConstructor]
public Monitor(INotify notify)
{
_notify = notify;
}
Running the code produces the same result.
2. Property Injection
For property injection, add the [Dependency] attribute so that when the container creates an instance, it automatically instantiates the dependent object and injects it into the property.
Modify the Monitor class:
public class Monitor : IMonitor
{
[Dependency]
public INotify Notifier { get; set; }
public void Alarm()
{
Notifier.Send();
}
}
In the Main function, modify the registration to inject an EmailNotify instance:
container.RegisterType<INotify, EmailNotify>();
Additionally, you can specify a name for the Dependency attribute, which will inject the entity registered with that name:
public class Monitor : IMonitor
{
[Dependency("SMS")]
public INotify Notifier { get; set; }
public void Alarm()
{
Notifier.Send();
}
}
Update the Main function to register named instances:
container.RegisterType<INotify, EmailNotify>("Email");
container.RegisterType<INotify, SMSNotify>("SMS");
3. Method Call Injection
Method call injection differs from constructor injection in timing. Constructor injection occurs when the container creates the instance, while method call injection happens when the method is called. To implement method call injection, add the [InjectionMethod] attribute to the method.
Modify the Monitor class:
public class Monitor : IMonitor
{
private INotify _notify;
[InjectionMethod]
public void InitializeNotifier(INotify notify)
{
_notify = notify;
}
public void Alarm()
{
_notify.Send();
}
}
At runtime, the container automatically creates the dependency object for the method, calls the method, and injects it.
Main functon:
static void Main(string[] args)
{
IUnityContainer container = new UnityContainer();
container.RegisterType<IMonitor, Monitor>();
container.RegisterType<INotify, EmailNotify>();
IMonitor monitor = container.Resolve<IMonitor>();
monitor.Alarm();
Console.ReadLine();
}