In short, Lazy
So, instead of directly creating an instance of CostlyObject (costly either in terms of initialization time or memory consumption), we create an instance of Lazy<CostlyObject> and use the .Value property in order to access any method or property.
Lazyp1 = new Lazy (()=> new Person("xose", 34));
Console.WriteLine(p1.Value.SayHi());
You can see the full code here.
I pretty like it, but I see a problem with it, its usage is not transparent. The client code is fully aware of being working with a proxy object and needs to adapt to its semantics (use myLazy.Value.Whatever instead of myObject.Whatever). We can think this is OK, but well, we could think of "lazyness" as an implementation detail that we don't want to share with the client.
Even worse, this won't work for already existing code. There's not inheritance relationship between Lazy<T> and T, so we can't happily pass a Lazy<T> object to a piece of code expecting a T object...
All this I've said above immediately rings a bell for me, Dynamic Proxies and Interception. This time we don't want to intercept calls to log something, but to create the real object.
So, time again for Castle DynamicProxy.
My first intention was to use a Class Proxy. This does not make sense. I would be creating an instance of a class inheriting from the T class (ProxyGenerator.CreateClassProxy<T> creates that new class and returns an instance of that class). Given how constructors and inheritance work, any child class constructor need to call a Base class constructor (either in an explicit or implicit fashion) so I would be building the costly object since the first moment (unless I were using some trick of having a dumb Base constructor that is not costly, but in the end is not useful...) so that's not the way to go.
As we have a problem with class inheritance here, we'll have to use a different approach, Interface implementation.
As you can read in this excellent tutorial, we have 3 options when it comes to interface proxies. 2 of them have the same problem as the previous mechanism, thought the created proxy is implementing an interface, it inherits from the proxied class, so it's useless for this. But we have a third option, Proxy without target:
Proxy without target. This one is tricky. You don’t supply target implementation for the interface. Dynamic Proxy generates it for you at runtime, using interceptors to provide behavior for its methods.
So, we generate a proxy class that implements the interface, and use an interceptor to lazy create and reference the instance of the real class.
We have 2 classes then, one that generates the Proxy, and another one for the Interceptor
public class LazyGenerator
{
ProxyGenerator generator = new ProxyGenerator();
//T is an interface
public T GetLazy(Func constructor) where T:class
{
T proxy = generator.CreateInterfaceProxyWithoutTarget(new LazyInterceptor (constructor));
return proxy;
}
}
public class LazyInterceptor: IInterceptor where T:class
{
private T target;
private Funcconstructor;
public LazyInterceptor(Funcconstructor)
{
this.constructor = constructor;
}
public void Intercept(IInvocation invocation)
{
Console.WriteLine("intercepted");
this.target = this.target ?? this.constructor();
//now invoke the method in the real object
//this works fine both for methods and properties, as in the case of a property this invocation.Method would be the get_propertyName, set_propertyName
invocation.ReturnValue = invocation.Method.Invoke(this.target, invocation.Arguments);
}
}
The constructor delegate passed to the Interceptor contains the invocation to constructor of the proxied class (it's just the same approach used by Lazy<T>.
You've got a full sample here where I lazy create a Repository. (zip with the needed Castle Assembly here).
As you can see in the comments it's interesting how this line in the interceptor:
invocation.Method.Invoke(this.target, invocation.Arguments);
works both for intercepted method calls and properties.
This is pure logic, a C# property "X" gets translated by the C# compiler into a "get_X" and a "set_X" method, so that's what we have in the invocation.Method when we access a property through the proxy.
If you happen to read this blog regularly (which I doubt :-)) you'll probably have realized that this entry is rather related to this previous one.
No comments:
Post a Comment