Unity Interception Techniques
In order to perform interception, Unity must be able to capture the original call and pass it through a behaviors pipeline to the target object, then pass the result back through the behaviors pipeline to the original caller. The two common approaches to the interception process are instance interception and type interception, and Unity provides techniques for both. Instance interceptors work by creating a proxy to the intercepted instance. Type interceptors work by deriving a new type that implements interception. The instance interception target is the original, non-intercepted object, but when performing type interception the target is intercepted and a derived object used.
Note
Instance interception works only on instance methods; it does not work for static methods or constructors since the constructor has already executed by the time the client code gets back an interception-ready object. Instance interception can only intercept public instance methods. Type interception can intercept public and protected methods.
This topic contains the following sections that will help you to understand interception:
- Instance Interception
- Type Interception
- Comparison of Interception Techniques
- Summary of Interception Approaches
Instance Interception
Instance interception works with both existing instances of objects, and with new instances created by Unity. The following schematic shows the basic process of instance interception.
When the application resolves the object through the Unity container, the Unity interception container extension manages the process. It obtains a new or existing instance of the object from the container, and creates a proxy to the object. Then it creates the handler pipeline and connects it to the target object before returning a reference to the proxy. The client then calls methods and sets properties on the proxy as though it were the target object.
Note
Unity interception can be used without a Unity DI container by using the stand-alone API through the static Intercept class. For more information, see Using Interception in Applications.
These calls pass through the interception behaviors, executing the preprocessing stage of each one, with the final behavior in the chain passing the call to the target object. The return value from the target object passes back through the behaviors in the reverse order, executing the post-processing stage of each one. The first behavior in the pipeline then passes the result back to the caller.
Instance interception is the most common and widely used technique. It can be used with objects that either implement the MarshalByRefObject abstract class, or implement a public interface that defines all of the methods to be intercepted. Unity provides the two interceptors, TransparentProxyInterceptor and InterfaceInterceptor, that support these two scenarios. For more details, see the tables in Comparison of Interception Techniques later in this topic.
Type Interception
Type interception uses a derived class instead of a proxy. As described in the previous section, instance interception works by creating a proxy to the target object. Type interception, on the other hand, more closely resembles aspect-oriented programming (AOP) techniques common in Java-based systems. Type interception avoids the possible performance penalties of using a proxy object by dynamically deriving a new class from the original class, and inserting calls to the behaviors that make up the pipeline. The following schematic shows the basic process of type interception.
When the application resolves the required type through the Unity container, the Unity interception container extension creates the new derived type and passes it, rather than the resolved type, back to the caller. Because the type passed to the caller derives from the original class, it can be used in the same way as the original class. The caller simply calls the object, and the derived class will pass the call through the behaviors in the pipeline just as is done when using instance interception.
Note
Unity interception can be used without a Unity DI container by using the stand-alone API through the static Intercept class. For more information, see Using Interception in Applications.
However, due to the nature of the dynamic type generation, there are some limitations with this approach. It can only be used to intercept public and protected virtual methods, and cannot be used with existing object instances. In general, type interception is most suited to scenarios where you create objects especially to support interception and allow for the flexibility and decoupling provided by policy injection, or when you have mappings in your container for base classes that expose virtual methods. For more details, see the tables of comparisons in the following topic Comparison of Interception Techniques.
Comparison of Interception Techniques
The previous sections demonstrated how it is important to choose the appropriate interception technique based on your requirements and the type of object you want to intercept. The following table lists the three interceptor classes included in Unity, and describes when you should use each type.
Type |
Description |
Use |
---|---|---|
Transparent Proxy Interceptor |
An instance interceptor. The proxy is created by using the .NET TransparentProxy/RealProxy infrastructure. |
When the type to intercept is a MarshalByRefObject or when only methods from the type's implemented interfaces need to be intercepted. |
Interface Interceptor |
An instance interceptor. It can proxy only one interface on the object. It uses dynamic code generation to create the proxy class. |
When resolving an interface mapped to a type. |
Virtual Method Interceptor |
A type interceptor. It uses dynamic code generation to create a derived class that is instantiated instead of the original intercepted class, and to hook up the behaviors. |
When only virtual methods need to be intercepted. |
Selection of a specific interceptor depends on your specific needs, because each one has various tradeoffs. The following table summarizes the three interceptors and their advantages and disadvantages.
Type |
Advantages |
Disadvantages |
---|---|---|
Transparent Proxy Interceptor |
Can intercept all methods of the target object (virtual, non-virtual, or interface). |
The object must either implement an interface or inherit from System.MarshalByRefObject. If the marshal by reference object is not a base class, you can only proxy interface methods. The Transparent Proxy process is much slower than a regular method call. |
Interface Interceptor |
Allows interception on any object that implements the target interface. It is much faster than the TransparentProxyInterceptor. |
It only intercepts methods on a single interface. It cannot cast a proxy back to the target object's class or to other interfaces on the target object. |
Virtual Method Interceptor |
Calls are much faster than the Transparent Proxy Interceptor. |
Interception only happens on virtual methods. You must set up interception at object creation time and cannot intercept an existing object. |
Summary of Interception Approaches
Unity provides instance and type interception with intercepted objects for which you have obtained a reference either through the container, or by using the stand-alone API to explicitly intercept a known instance. Instance interceptors use a separate proxy object between your code and your target object. Using interception, you make a call on the proxy object instead of directly calling the target object. The proxy invokes the various interception behaviors, and then it forwards the call to the target object. Different implementations of instance interceptors can have different constraints. Instance interceptors have the following characteristics:
- They can intercept objects created by the container.
- They can intercept objects not created by the container.
- The TransparentProxyInterceptor can intercept more than one interface, and marshal-by-reference objects.
- The InterfaceInterceptor can only be used to intercept a single interface.
Type interceptors create a new type that inherits from the target type. This new type is then instantiated instead of the original type you requested. The new type overrides all the virtual methods on the original target type. Type instance interceptors have the following characteristics:
- Only one object is created; there is no proxy object between the caller and the new object.
- The new object has full type compatibility because it is derived from the target type.
- They are able to intercept objects only at creation, and cannot intercept existing instances.
- They can only intercept virtual public and protected methods.
The system that Unity uses to automatically create a derived target object, or a proxy and behaviors pipeline, is similar to the aspect-oriented programming (AOP) approach. However, Unity is not an AOP framework implementation for the following reasons:
- It uses interception to enable only preprocessing behaviors and post-processing behaviors.
- It does not insert code into methods, although it can create derived classes containing policy pipelines.
- It does not provide interception for class constructors.