This is part six of my tutorial on Castle Dynamic Proxy.
We talked about how Dynamic Proxy proxying mechanism works, but so far we only briefly spoke about its limitations. The main limitation is the fact, that it can not intercept non-virtual calls. This may be an issue of varying importance.
In case of libraries such as NHibernate, which uses DynamicProxy for lazy loading, when your member is not virtual, you can't lazy load it. This may be a drawback for performance, but your program should be able to work nonetheless.
On the other hand, for libraries like Rhino.Mocks, or MoQ, that use DynamicProxy for creating mocks and stubs, not being able to intercept a call, means you can’t mock its behavior and the response. This is a critical issue.
Similarly in case of our sample Freezable library, if a setter is not virtual, we can't intercept calls to it, and that means we can not warrant that the state of the object will not change.
When we talked about ProxyGenerationHook, I mentioned that it has a NonVirtualMemberNotification method, that gets called when proxy generator encounters method it can not proxy.
In case of freezable library, we took simplistic assumption that state of the object can only change via property setters. In this case if just any method we encounter is non-virtual we don't care. Since we assume that it can't change the state of our object anyway, we may as well let it be non-virtual and still deliver on our promise.
However, if we encounter a non-virtual property setter, we should throw an exception to indicate that we deny taking any responsibility for this objects immutability.
Enough theory — let's write some tests to get a cleaner picture of what we want to achieve.
[Fact]
public void Freezable_should_freeze_classes_with_nonVirtual_methods()
{
var pet = Freezable.MakeFreezable<WithNonVirtualMethod>();
pet.Name = "Rex";
pet.NonVirtualMethod();
}
[Fact]
public void Freezable_should_throw_when_trying_to_freeze_classes_with_nonVirtual_setters()
{
var exception = Assert.Throws<InvalidOperationException>( () =>
Freezable.MakeFreezable<WithNonVirtualSetter>() );
Assert.Equal(
"Property NonVirtualProperty is not virtual. Can't freeze classes with non-virtual properties.",
exception.Message );
}
If we run the tests the first one will pass, the second one will fail. Is that a surprise? We don't have any checking code in place, so when we encounter a non virtual member, we do nothing. that's why we effectively ignored the NonVirtualMethod in the first test (which is the behavior we want), but we also ignored the non-virtual property setter in the second test, which is a no-no.
To make them pass, we need to actually implement the NonVirtualMemberNotification method, to throw an exception when it encounters a property setter. There's no specific exception type we're obligated to throw, so we'll just throw InvalidOperationException as specified in our test.
Here's the implementation:
public void NonVirtualMemberNotification(Type type, MemberInfo memberInfo)
{
var method = memberInfo as MethodInfo;
if(method!=null)
{
this.ValidateNotSetter( method );
}
}
private void ValidateNotSetter(MethodInfo method)
{
if( method.IsSpecialName && IsSetterName( method.Name ) )
throw new InvalidOperationException(
string.Format(
"Property {0} is not virtual. Can't freeze classes with non-virtual properties.",
method.Name.Substring( "set_".Length )
)
);
}
Similar to ShouldInterceptMethod method MarshalByRefObject and Object classes are special cases, and by default DynamicProxy will just ignore them.
With this change, all our tests now pass.
Other than the two methods we already discussed, IProxyGenerationHook has one more method: MethodsInspected. It gets called, after all methods on proxied type have been inspected, so it is the last method to be called by proxy generator. It is useful in cases when you hold some precious resources needed by two other methods.
For example if our hook implementation asked some external service (like a WCF service or database) about what to do with non-virtual methods, MethodsInspected would be the place to close the connection and dispose of all resources that are no longer needed.
With this part we basically covered almost all of basics of Dynamic Proxy. In the next part we’ll discuss other kinds of proxies you can create with Dynamic Proxy (yes, there are a few). Then we’ll talk about more advanced scenarios, like mixins.
I’m still open for feedback, so if you feel I missed some important topic or should have expand on something let me know in the comments.
The code, as always, is here.