D41d8cd98f00b204e9800998ecf8427e

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.

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 !

0bb10b6560de9804f6693eb85879816d

BGilner

March 28, 2011, March 28, 2011 18:32, permalink

No rating. Login to rate!

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();
        }
    }
}
F9a9ba6663645458aa8630157ed5e71e

Ants

March 28, 2011, March 28, 2011 20:06, permalink

No rating. Login to rate!

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?

0bb10b6560de9804f6693eb85879816d

BGilner

March 28, 2011, March 28, 2011 20:11, permalink

No rating. Login to rate!

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.

Your refactoring





Format Copy from initial code

or Cancel