Castle Dynamic Proxy tutorial part XII: caching

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

If you’ve been fol­low­ing the tuto­r­ial, you should remem­ber that Cas­tle Dynamic Proxy pro­vides prox­y­ing capa­bil­i­ties by gen­er­at­ing types at run­time. Dynamic code gen­er­a­tion is not a light­weight oper­a­tion, so pretty impor­tant aspect of Dynamic Proxy is its caching mech­a­nism which we’ll going to cover in this post.

Let’s con­sider the fol­low­ing piece of a test:

var proxy1 = generator.CreateClassProxy<Foo>(new FooInterceptor());
var proxy2 = generator.CreateClassProxy<Foo>(new BarInterceptor(), new FooInterceptor());
Assert.AreEqual(proxy1.GetType(), proxy2.GetType());

Will it suc­ceed? The answer is – yes. In this sim­ple case both prox­ies would be seman­ti­cally iden­ti­cal, so gen­er­a­tor (or more pre­cisely IProx­y­Builder that the gen­er­a­tor is using) caches the type that has been used dur­ing the first call. Dur­ing the sec­ond call it checks the cache to see if a type meet­ing it’s cri­te­ria already exists, it finds it, so it skips the gen­er­a­tion part and moves straight into cre­at­ing the instance.

What

What is taken into con­sid­er­a­tion when Dynamic Proxy decides if a type can be reused or not?

  • proxy tar­get type obvi­ously. We were cre­at­ing two prox­ies for Foo, so the type could be reused. if the other type was proxy for Bar, it would not be able to reuse proxy of Foo, so a new type would be created.
  • addi­tional inter­faces to proxy. If we decided that the sec­ond proxy should also imple­ment an IBar the first type would not be reused, since it would not imple­ment the inter­face, so a new type would be created.
  • type of proxy tar­get object. If we were cre­at­ing inter­face proxy with tar­get, and one imple­men­ta­tion would be of type Foo, and the other of type Bar, a new type would be cre­ated. Does this feel coun­ter­in­tu­itive? We’re cre­at­ing inter­face proxy so why should imple­menter type be taken into account, right? Well, recall from the pre­vi­ous part, that addi­tional inter­faces are imple­mented dif­fer­ently depend­ing on whether the tar­get object imple­ments them or not, hence it is impor­tant and proxy type could not be reused among dif­fer­ent ones.
  • proxy gen­er­a­tion options. They obvi­ously affect proxy gen­er­a­tion so it’s nat­ural that when they dif­fer a new type needs to be cre­ated. Here’s which ele­ments of proxy gen­er­a­tion options affect caching
    • proxy gen­er­a­tion hook. They affect which meth­ods get prox­ies, and which not, so if for two proxy gen­er­a­tion options hooks are dif­fer­ent, cached type will not be reused.
    • mixin types. Log­i­cal, same types, cached type can be reused.
    • inter­cep­tor selec­tor. Well this is not very log­i­cal. Since inter­cep­tor selec­tor oper­ates on proxy instances, it should be trans­par­ent to the cache. How­ever it’s not. This is a result of design deci­sion that inter­cep­tor selec­tor is shared among all instances of a proxy type. Dif­fer­ent selec­tors could mean dif­fer­ent behav­ior, hence the type could not be reused.
    • base type for inter­face proxy. The default is System.Object. How­ever if you changed it, a new type would have to be gen­er­ated for a new proxy obviously.

How

As you can see there are quite a few ingre­di­ents that affect caching. There are few things you can make to improve your usage of cached types and decrease num­ber of types that would have to be generated.

When you pro­vide proxy gen­er­a­tion hook, or inter­cep­tor selec­tor always over­ride Equals and GetH­ash­Code meth­ods. The default imple­men­ta­tion com­pare ref­er­en­tial equal­ity which in most cases is not what you’d want. Two selec­tors, or hooks may expose exactly the same behav­ior, but if the type does not over­ride equal­ity meth­ods to tell that fact to the out­side world they will be con­sid­ered different.

Where

If instead of the code above we had this:

var proxy1 = generator1.CreateClassProxy<Foo>(new FooInterceptor());
var proxy2 = generator2.CreateClassProxy<Foo>(new BarInterceptor(), new FooInterceptor());
Assert.AreEqual(proxy1.GetType(), proxy2.GetType());

Notice we now gen­er­ate each proxy with dif­fer­ent gen­er­a­tor, would the types be the same? The answer is – it depends.

As I said, gen­er­a­tor uses IProx­y­Builder which pro­vides it with proxy types. Builder uses Mod­ule­Scope which man­ages the cache. So if the two gen­er­a­tors had the same Mod­ule­Scope behind them the answer would be yet – we would get the same type. How­ever if they had dif­fer­ent scope each gen­er­a­tor would pro­duce its own type.

This is impor­tant to under­stand, but in real life you’ll prob­a­bly need just one proxy gen­er­a­tor any­way, hence all the prox­ies would be cre­ated in the same scope, which usu­ally is the best option.

Comments are closed.