Tripping over logs: A story of Unity - Part 5
Recap + Intro
In part 1, we looked at the problem with just adding logging code and how it quickly gets messy. We also saw how the more loosely coupled the code is, the easier it is to effect changes painlessly. Then in part 2, we took that code and added Unity, giving us control via a config file as to what class our logging should use. In part 3, we saw some benefits of dealing with this overhead. Previously, we actually got our logging nicely sorted, but there were a few extra details worth mentioning, so here goes.
Note: You can get Unity with the Enterprise Library from the p&p group.
As I said in part 1:
What I am going to do is look at a practical approach to using Unity. I will not be going into DI or IoC—those topics have been covered far better by smarter people than me. If you want the theory, look elsewhere; if you want to get running with Unity, this is for you.
This is a multi-part series, so here’s the series guide in case you’re looking for the rest:
- Part 1 - Introduction to the problem
- Part 2 - Changing the code to use basic Unity functions
- Part 3 - Lifetime management
- Part 4 - Changing the code to use interception
- Part 5 - Interception supplementary
- Part 6 - Wrap up
Circular References
DI is actually a minefield, since you can cause a circular reference easily, but injection makes that even easier to shoot yourself in the foot. What is a circular reference? The documentation (shockingly) describes it well:
- Objects generated through constructor injection that reference each other in their constructor parameters
- Objects generated through constructor injection where an instance of a class is passed as a parameter to its own constructor
- Objects generated through method call injection that reference each other
- Objects generated through property (setter) injection that reference each other
For example, the following code shows two classes that reference each other in their constructors:
public class Class1
{
public Class1(Class2 test2) { ... }
}
public class Class2
{
public Class2(Class1 test1) { ... }
}
It is the developer’s responsibility to prevent this type of error by ensuring that the members of classes they use with dependency injection do not contain circular references.
I’ve bolded that last line to emphasize the point. You’ve been warned!
Reflection
It’s not just interception that uses reflection—other parts do as well, so make sure you understand the security implications: http://msdn.microsoft.com/en-us/library/9syytdak.aspx
Interceptors
I used the TransparentProxyInterceptor in my previous post, but there are two others. Each has pros and cons, which the documentation summarizes in a simple table:
| Type | Description | Use |
|---|---|---|
| TransparentProxyInterceptor | An instance interceptor. The proxy is created 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. |
| InterfaceInterceptor | An instance interceptor. It can proxy only one interface on the object and uses dynamic code generation to create the proxy class. | When resolving an interface mapped to a type. |
| VirtualMethodInterceptor | A type interceptor. It uses dynamic code generation to create a derived class that gets instantiated instead of the original, intercepted class and to hook up the call handlers. | When only virtual methods need to be intercepted. |
Selection of a specific interceptor depends on your needs, as each has trade-offs. The following table summarizes the three interceptors and their advantages and disadvantages:
| Type | Advantages | Disadvantages |
|---|---|---|
| TransparentProxyInterceptor | 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 TransparentProxy process is much slower than a regular method call. |
| InterfaceInterceptor | Proxy supports casting to all interfaces or types of the target object. | 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. |
| VirtualMethodInterceptor | Calls are much faster than the TransparentProxyInterceptor. | Interception only happens on virtual methods. You must set up interception at object creation time and cannot intercept an existing object. |
Rules
There is an entire rules framework available in interception, letting you enable/disable interception based on rules. The idea is that you annotate methods and then use the rules to filter which ones to actually run. As with everything in Unity, this can be configured in code or in the configuration file. Here’s a list of the rules (you can probably guess what they do by their names):
- The Assembly Matching Rule
- The Custom Attribute Matching Rule
- The Member Name Matching Rule
- The Method Signature Matching Rule
- The Namespace Matching Rule
- The Parameter Type Matching Rule
- The Property Matching Rule
- The Return Type Matching Rule
- The Tag Attribute Matching Rule
- The Type Matching Rule
And, as always, you can develop your own custom rules too.