Castle Dynamic Proxy tutorial part XI: When one interface is not enough

This is part eleven of my tuto­r­ial on Cas­tle Dynamic Proxy.

So far in the tuto­r­ial we’ve cov­ered most of the basics. How­ever, we were always prox­y­ing just one type, be it a class or an inter­face. There is quite often a need to do more. What if a class imple­ments more than one inter­face, and we want them all prox­ied? What if we want the proxy to imple­ment inter­faces the tar­get type does not imple­ment. Today we’ll talk about how to do just that, so let’s get straight to it.

If you look into the API you may notice that for every kind of proxy there are over­loads that take array of types called additionalInterfacesToProxy.

dptutorial_11_intellisense

This is your gate­way into extend­ing your proxy with addi­tional capa­bil­i­ties. How­ever, as sim­ple as it may seem, there are few things you should be aware of when using addi­tional interfaces.

Class which does not imple­ment the interface

Let’s start with sim­ple exam­ple of class proxy.

[Fact]
public void ClassProxy_should_implement_additional_interfaces()
{
	object proxy = generator.CreateClassProxy(
		typeof(EnsurePartnerStatusRule),
		new[] {typeof(ISupportsInvalidation)},
		new InvalidationInterceptor());

	Assert.IsAssignableFrom<ISupportsInvalidation>(proxy);
}

The actual imple­men­ta­tion of EnsurePart­ner­Sta­tus­Role is not impor­tant right now, what’s impor­tant is that it does not imple­ment the ISup­port­sIn­val­i­da­tion inter­face. The tests passes how­ever, so the result­ing proxy does imple­ment it (hooray!). How do you think that inter­face gets implemented?

If you remem­ber inter­face prox­ies with­out tar­get that would be basi­cally it. You get an empty stub of a method, and you more or less have to use inter­cep­tors to fill it with logic. Since there is no tar­get the last inter­cep­tor in the pipeline must not call invocation.Proceed() since there’s no actual imple­men­ta­tion we could pro­ceed to.

Class which imple­ments the interface

What if the type did imple­ment the inter­face? Let’s see another test.

[Fact]
public void ClassProxy_for_class_already_implementing_additional_interfaces()
{
	object proxy = generator.CreateClassProxy(
		typeof(ApplyDiscountRule),
		new[] {typeof(ISupportsInvalidation)});
	Assert.IsAssignableFrom<ISupportsInvalidation>(proxy);
	Assert.DoesNotThrow(() => (proxy as ISupportsInvalidation).Invalidate());
}

Notice we didn’t pro­vide any inter­cep­tor, so if the proxy does not for­ward the call to the Apply­Dis­coun­tRule’ imple­men­ta­tion the sec­ond assert will fail. So will the test pass?

The answer is – it will not. We still get a method with­out tar­get. This is sur­pris­ing (I am sur­prised), as this is prob­a­bly not what most of you would expect. I actu­ally think this is an omis­sion and it is likely going to change so that the test would pass but any­way, it’s good to keep it in mind in order to avoid bugs.

This obvi­ously works pre­cisely the same way for inter­face proxy with­out tar­get, but what about inter­face proxy with target?

Inter­face proxy with target

Let’s see a test:

[Fact]
public void InterfaceProxy_should_implement_additional_interfaces()
{
	object proxy = generator.CreateInterfaceProxyWithTarget(
		typeof(IClientRule),
		new[] {typeof(ISupportsInvalidation)},
		new ApplyDiscountRule());
	Assert.IsAssignableFrom<ISupportsInvalidation>(proxy);
	Assert.DoesNotThrow(() => (proxy as ISupportsInvalidation).Invalidate());
}

We obvi­ously need the Apply­Dis­coun­tRule type to imple­ment IClien­tRule inter­face, but what about ISup­port­In­val­i­da­tion? Notice that for this test also we didn’t pass any inter­cep­tor. So will the sec­ond assert succeed?

The answer is… well not that obvi­ous to every­one so pay atten­tion – it depends. Actu­ally the first answer is pretty straight­for­ward – it does not have to imple­ment the addi­tional inter­face, but the sec­ond one depends on whether it does or not.

If it does, then the dynamic proxy will pick it up and use that as tar­get. Calls to mem­bers of ISup­port­sIn­val­i­da­tion will be no dif­fer­ent than calls to mem­bers of IClien­tRule. If it does not, we’re back in square A – we have no imple­men­ta­tion to for­ward to so we need to del­e­gate that respon­si­bil­ity to inter­cep­tors. This is prob­a­bly what most peo­ple would expect any­way. There’s one thing, which although not really related to Dynamic Proxy could cre­ate some confusion.

We pass a tar­get object to the method and it’s the actual type of the object that gets exam­ined for pres­ence of addi­tional inter­faces, even if you would pass it around via ref­er­ence to it’s base type which does not imple­ment the inter­face, still if the actual type does imple­ment it, it would be used as target.

Inter­face proxy with tar­get interface

What about the last kind of proxy we have?

In case our tar­get does not imple­ment the addi­tional inter­face we get the same behav­ior as in every other case. In case it does how­ever, we sur­pris­ingly get this:

dptutorial_11_so

Sur­prised? Well – don’t be because it’s not a bug – it’s a fea­ture! Let me walk you through:

Remem­ber that in case of inter­face proxy with tar­get an inter­cep­tor can change the tar­get dur­ing the invo­ca­tion. Also since the actual tar­get of the proxy is the main inter­face, dynamic proxy does not exam­ine the object passed as tar­get for pres­ence of any of the addi­tional inter­faces. It would make no sense, since you can swap it out any­way – right?

So when we invoke a method from any of the addi­tional inter­faces the only tar­get we have is the proxy itself, and that’s what is passed to the invo­ca­tion. Then, since no inter­cep­tor changed the tar­get, when we invoke the method on tar­get, we invoke it on the proxy itself which starts the vicious cir­cle again.

So you still need an inter­cep­tor, to do either one of two things to break the circle:

  • Swap the tar­get of an invo­ca­tion with some other object that imple­ments it.
  • Not call invocation.Proceed() and han­dle the invo­ca­tion for itself.

Notice that if you want to go with option nr 1 you can grab the tar­get object passed to the method cre­at­ing the proxy (using IProx­y­Tar­ge­tAc­ces­sor) and after mak­ing sure it imple­ments the inter­face at hand, set­ting it as the tar­get of the invocation.

Sum­mary

Although on the sur­face it seems like a very sim­ple fea­ture there is quite a lot to learn about addi­tional inter­faces. How­ever they are pretty pow­er­ful and well worth the learn­ing effort. Although I didn’t pro­vide any spe­cific sce­nario for today (sorry, I don’t feel very cre­ative right now) there are plenty places you could use this fea­ture (throw­ing INo­ti­fyProp­er­ty­Changed on top of your domain model seems to be one of the most pop­u­lar and useful).