A C# TypeLoaderInterceptor
What are your thoughts on this?
Java has the concept of class loader. I cannot define myself as a java expert, I can simply read and understand the code, nothing more, but as far as I have understood class loaders are a really cool feature because they let you inject your own logic into the type loading process.
As with all powerful features, class loaders must be used with caution.
In the .Net world we do not have anything built-in that can be compared to class loaders. Using PostSharp and a bunch of lines of good-old C# code we can achieve something that is not a real class loader but is much more comparable to type loader interceptor. Well, java class loaders does, technically, a really different thing from what I’m doing here, class loaders have a chance to modify the bytecode at runtime (at class load time), instead PostSharp modifies the IL at compile time letting the developer introduce custom code that will be executed at runtime.
So what?
Let’s start with a really simple class:
And I want these tests to pass:public class TestClass { public TestClass() { this.Guid = Guid.Empty; } public Guid Guid { get; set; } }
test execution gives:[TestMethod] public void TypeLoadEngine_intercept_using_valid_interceptor_should_intercept_instance_creation() { var obj = new TestClass(); obj.Guid.Should().Not.Be.EqualTo( Guid.Empty ); } [TestMethod] public void TypeLoadEngine_intercept_using_valid_interceptor_and_activator_create_instance_should_intercept_instance_creation() { var obj = Activator.CreateInstance<TestClass>(); obj.Guid.Should().Not.Be.EqualTo( Guid.Empty ); } [TestMethod] public void TypeLoadEngine_intercept_using_valid_interceptor_and_multiple_class_instance_should_intercept_each_instance_creation() { var obj1 = new TestClass(); var obj2 = new TestClass(); obj1.Guid.Should().Not.Be.EqualTo( obj2.Guid ); }
Figure: I expect these to fail, but they passed.
Interesting…isn’t it?
But why?
Before moving on looking at the code you may wonder what I’m trying to achieve, basically the requirement is:
As a developer I do not want to use an ObjectFactory pattern to create instances of the domain model but I need to inject some behavior in my classes without the need to manually do that.A real world example?
Yes, quite easy. You are building something that is strongly based on DDD and you are using event sourcing strategies to propagate domain events in the whole model but you do not want to use a “static” event broker in your domain, instead you want to inject one using your favorite Inversion of Control container. Basically you do not want this:
where Broker.Current is a static reference to the broker, but you want this:public class Message { public Boolean IsRead { get; protected set; } public void MarkAsRead() { if( !this.IsRead ) { this.IsRead = true; Broker.Current.Dispatch( new MessageMarkedAsRead( this ) ); } } }
One obvious reason is testing.public virtual void MarkAsRead() { if( !this.IsRead ) { this.IsRead = true; this.Broker.Dispatch( new MessageMarkedAsRead( this ) ); } }
So...where’s the magic?
Let’s dive into the solution, first let’s introduce the TypeLoader Engine:
And immediately after the ITypeLoadInterceptor:[TestInitialize] public void Setup() { TypeLoader.Engine.AddInterceptor( new TestInterceptor() ); } [TestCleanup] public void Teardown() { TypeLoader.Engine.Shutdown(); }
What’s happening here?class TestInterceptor : ITypeLoadInterceptor { public bool IsInterestedIn( object instance, TypeLoadMomentum momentum ) { return instance is TestClass && momentum == TypeLoadMomentum.AtConstructorExit; } public void Intercept( object instance, TypeLoadMomentum momentum ) { var p = ( TestClass )instance; p.Guid = Guid.NewGuid(); } }
Basically the TypeLoader Engine is just a way to hold a globally known list of interceptors, nothing more, internally it just holds a List
Now every single piece is finding its own place in the puzzle, we simply miss one little thing…
Who is wiring the two worlds?
As I said I have a great 3rd party utility called PostSharp - that is doing the magic under the covers:
The InterceptTypeLoadAttribute is an aspect we build to instruct the PostSharp post compiler to “inject” some code at compile time into our TestClass in order to wire the two world, our attribute is an OnMethodBoundaryAspect that basically does 2 things:[assembly: InterceptTypeLoadAttribute( AttributeTargetTypes = "*.TestClass" )]
- At compile time PostSharp gets the chance to decide whether or not it should be applied to a target type based on the location where it is. We are only interested in constructors;
- At runtime PostSharp intercepts the constructor call and dispatches one call to the type loader engine before the constructor begins and one call just before the constructor ends;
The following is a screenshot, grabbed using ILSpy, of the enhanced class post-compiled by PostSharp:[Serializable] [AttributeUsage( AttributeTargets.Assembly | AttributeTargets.Constructor | AttributeTargets.Class )] public class InterceptTypeLoadAttribute : OnMethodBoundaryAspect { public override void OnEntry( MethodExecutionArgs args ) { base.OnEntry( args ); TypeLoader.Engine.Intercept( args.Instance, TypeLoadMomentum.AtConstructorEnter ); } public override void OnSuccess( MethodExecutionArgs args ) { base.OnSuccess( args ); TypeLoader.Engine.Intercept( args.Instance, TypeLoadMomentum.AtConstructorExit ); } }
Figure: The constructor of the TestClass with all the code injected by PostSharp, highlighted in yellow the two call that allows the wiring between to the two worlds.
Do you like it as much as I do? Grab it from nuget
Tell me if you would use it in your own project or leave a comment for any suggestion. I’d like to thank Adam Cogan, Peter Gfader and Gael Fraiteur (of Sharp Crafters) for reviewing this post.
.m