using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Remoting.Proxies;
using System.Threading;
/// <summary>
/// A proxy for an object which instantiates the object only when it is needed.
/// </summary>
[DebuggerDisplay("RealProxy: {GetTransparentProxy().ToString()}")]
public abstract class LazyObject : RealProxy {
/// <summary>
/// Creates a lazy object that calls the function the first time it needs to be used.
/// </summary>
public static T Create<T>(Func<T> func) where T : MarshalByRefObject {
LazyObject proxy = new Delegate<T>(typeof(T), func);
return (T)proxy.GetTransparentProxy();
}
/// <summary>
/// Some methods need to be implemented without actually forcing the object.
/// </summary>
private static ThreadLocal<Dictionary<MethodBase, Func<LazyObject, object[], object>>> SelfImplementedMethods =
new ThreadLocal<Dictionary<MethodBase, Func<LazyObject, object[], object>>>(() => {
MethodBase GetType = typeof(object).GetMethod("GetType", BindingFlags.Instance | BindingFlags.Public);
MethodBase GetHashCode = typeof(object).GetMethod("GetHashCode", BindingFlags.Instance | BindingFlags.Public);
MethodBase Equals = typeof(object).GetMethod("Equals", BindingFlags.Instance | BindingFlags.Public);
MethodBase ToString = typeof(object).GetMethod("ToString", BindingFlags.Instance | BindingFlags.Public);
return new Dictionary<MethodBase, Func<LazyObject, object[], object>> {
{ GetType, (obj, args) => obj.targetType },
{ GetHashCode, (obj, args) => obj.GetHashCode() },
{ Equals, (obj, args) => obj.Equals(args[0]) },
{ ToString, (obj, args) => obj.hasValue ? obj.target.ToString() : "Uninstantiated lazy reference for a " + obj.targetType.Name }
};
});
private object locker = new object();
private MarshalByRefObject target;
private Type targetType;
private bool hasValue = false;
protected abstract MarshalByRefObject GetTargetObject();
protected LazyObject(Type type)
: base(type) {
targetType = type;
}
private void ForceTarget() {
if (!hasValue) {
lock (locker) {
if (!hasValue) {
target = GetTargetObject();
hasValue = true;
}
}
}
}
public override IMessage Invoke(IMessage message) {
IMethodCallMessage call = message as IMethodCallMessage;
if (call != null) {
Func<LazyObject, object[], object> method;
if (SelfImplementedMethods.Value.TryGetValue(call.MethodBase, out method)) {
// Method is implemented in the proxy, no need to force the target
object result = method(this, call.Args);
return new ReturnMessage(result, null, 0, null, call);
}
ForceTarget();
return RemotingServices.ExecuteMessage(target, call);
} else {
throw new InvalidOperationException("Unsupported message type " + message == null ? "null" : message.GetType().FullName);
}
}
/// <summary>
/// Creates a LazyObjectProxy from a delegate
/// </summary>
private class Delegate<T> : LazyObject where T : MarshalByRefObject {
private Func<T> func;
public Delegate(Type type, Func<T> func)
: base(type) {
this.func = func;
}
protected override MarshalByRefObject GetTargetObject() {
return (MarshalByRefObject)func();
}
}
}
using System;
namespace LazyTesting {
class MainClass {
public static void Main() {
TestObject[] test = new[] {
new TestObject(1, "Eager"),
LazyObject.Create(() => new TestObject(2, "Lazy")),
new TestObject(3, "Eager"),
LazyObject.Create(() => new TestObject(4, "Lazy"))
};
Console.WriteLine("Created objects");
Console.WriteLine("ToString():");
for (int i = 0; i < test.Length; i++) {
Console.WriteLine(" [" + i + "]: " + ((object)test[i]).ToString());
}
Console.WriteLine("Text:");
for (int i = 0; i < test.Length; i++) {
Console.WriteLine(" [" + i + "]: " + test[i].Text);
}
Console.WriteLine("ToString():");
for (int i = 0; i < test.Length; i++) {
Console.WriteLine(" [" + i + "]: " + test[i].ToString());
}
Console.ReadKey(true);
}
}
class TestObject : MarshalByRefObject {
public int Number { get; set; }
public string Text { get; set; }
public TestObject(int number, string text) {
this.Number = number;
this.Text = text;
Console.WriteLine("Creating TestObject: " + ToString());
}
}
}
Creating TestObject: LazyTesting.TestObject Creating TestObject: LazyTesting.TestObject Created objects ToString(): [0]: LazyTesting.TestObject [1]: Uninstantiated lazy reference for a TestObject [2]: LazyTesting.TestObject [3]: Uninstantiated lazy reference for a TestObject Text: [0]: Eager Creating TestObject: LazyTesting.TestObject [1]: Lazy [2]: Eager Creating TestObject: LazyTesting.TestObject [3]: Lazy ToString(): [0]: LazyTesting.TestObject [1]: LazyTesting.TestObject [2]: LazyTesting.TestObject [3]: LazyTesting.TestObject
Refactorings
No refactoring yet !
BGilner
March 28, 2011, March 28, 2011 18:32, permalink
I'd prefer this as it is slightly more concise. You could be even more concise (trading off validation) by drop GetObjectMethod and use a Dictionary of strings (like "GetType") instead of MethodBase and invoke it with: .TryGetValue(call.MethodBase.Name, out method))
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Remoting.Proxies;
using System.Threading;
/// <summary>A proxy for an object which instantiates the object only when it is needed.</summary>
[DebuggerDisplay("RealProxy: {GetTransparentProxy().ToString()}")]
public abstract class LazyObject : RealProxy
{
public delegate object LazyMethod(LazyObject lazyObject, object[] parms);
/// <summary>Creates a lazy object that calls the function the first time it needs to be used.</summary>
public static T Create<T>(Func<T> func) where T : MarshalByRefObject
{
var proxy = new Delegate<T>(func);
return (T)proxy.GetTransparentProxy();
}
private static MethodInfo GetObjectMethod(string methodName)
{
return typeof(object).GetMethod(methodName, BindingFlags.Instance | BindingFlags.Public);
}
/// <summary>Some methods need to be implemented without actually forcing the object.</summary>
private static readonly ThreadLocal<Dictionary<MethodBase, LazyMethod>> SelfImplementedMethods =
new ThreadLocal<Dictionary<MethodBase, LazyMethod>>(() => new Dictionary<MethodBase, LazyMethod>
{
{ GetObjectMethod("GetType"), (obj, args) => obj.targetType},
{ GetObjectMethod("GetHashCode"), (obj, args) => obj.GetHashCode()},
{ GetObjectMethod("Equals"), (obj, args) => obj.Equals(args[0])},
{ GetObjectMethod("ToString"), (obj, args) => obj.hasValue ? obj.target.ToString() : "Uninstantiated lazy reference for a " + obj.targetType.Name}
});
private readonly object locker = new object();
private MarshalByRefObject target;
private readonly Type targetType;
private bool hasValue;
protected abstract MarshalByRefObject GetTargetObject();
protected LazyObject(Type type)
: base(type)
{
targetType = type;
}
private void ForceTarget()
{
if (hasValue)
return;
lock (locker)
{
if (hasValue)
return;
target = GetTargetObject();
hasValue = true;
}
}
public override IMessage Invoke(IMessage message)
{
var call = message as IMethodCallMessage;
if (call == null)
throw new InvalidOperationException("Unsupported message type " + message == null ? "null" : message.GetType().FullName);
LazyMethod method;
if (SelfImplementedMethods.Value.TryGetValue(call.MethodBase, out method))
{
// Method is implemented in the proxy, no need to force the target
var result = method(this, call.Args);
return new ReturnMessage(result, null, 0, null, call);
}
ForceTarget();
return RemotingServices.ExecuteMessage(target, call);
}
/// <summary>Creates a LazyObjectProxy from a delegate</summary>
private class Delegate<T> : LazyObject
where T : MarshalByRefObject
{
private readonly Func<T> func;
public Delegate(Func<T> func)
: base(typeof(T))
{
this.func = func;
}
protected override MarshalByRefObject GetTargetObject()
{
return func();
}
}
}
Ants
March 28, 2011, March 28, 2011 20:06, permalink
I sort of like the concept of this class, but what happens if the class you are trying to use lazily actually has it's own private implementation of GetHashCode(), Equals(), and/or ToString()? Don't you effectively break the functionality of the actual object?
BGilner
March 28, 2011, March 28, 2011 20:11, permalink
Ants, that would be very easy to account for, just make it an opt-in class with a [LazyObject] attribute. Or you could modify GetObjectMethod to check for and invoke alternate implementations of those methods.
This class allows you to create lazy objects that are only created when they are actually needed; the lazy types need to derive from MarshalByRefObject, but may be sealed and don't require virtual methods or properties.