DynamicExtensions

Apr 29, 2011 at 5:13 AM
Edited Apr 29, 2011 at 5:17 AM

// Duck someObject = new Duck();  
    // dynamic duck = someObject.Extend();  
    // duck.Walk();
    // //Now modify this Walk() on the fly  
    // duck.Walk = new Action(() => System.Console.WriteLine("Duck Walking left"));  
    // //Call Walk again
    // duck.Walk();  

    public static class DynamicExtensions
    {
        public static dynamic Extend<T>(this T obj)
        {
            return new ExtensionWrapper(obj);
        }

        public static dynamic New<T>(this T t) where T : new()
        {
            return new ExtensionWrapper(new T());
        }
    }

    public class ExtensionWrapper : DynamicObject
    {

        /// <summary>The object we are going to wrap</summary>
        private readonly object _wrapped;

        /// <summary>A collection of methods</summary>
        private readonly Dictionary<string, Delegate> _methods = new Dictionary<string, Delegate>();

        /// <summary>Specify the flags for accessing members</summary>
        private const BindingFlags FLAGS = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public;

        /// <summary>Create a simple private wrapper</summary>
        public ExtensionWrapper(object o)
        {
            _wrapped = o;
        }


        public ExtensionWrapper(Type t)
        {
            _wrapped = t;
        }


        Type WrappedType
        {
            get
            {
                return (_wrapped is Type) ? _wrapped as Type : _wrapped.GetType();
            }
        }

        /// <summary>Try invoking a method</summary>
        public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
        {
            var types = from a in args select GetActualType(a);
            var mname = GetInternalName(binder.Name, args);

            if (_methods.ContainsKey(mname))
            {
                result = _methods[mname].DynamicInvoke(args);
                return true;
            }

            var method = WrappedType.GetMethod
                (binder.Name, FLAGS, null, types.ToArray(), null);

            if (method == null) return base.TryInvokeMember(binder, args, out result);

            result = method.Invoke(_wrapped, args);

            return true;
        }

        public Type GetActualType(object t)
        {
            return (t is ParameterInfo) ? (t as ParameterInfo).ParameterType : t.GetType();
        }

        /// <summary>Get the internal name for a method for housekeeping</summary>
        private string GetInternalName(string name, object[] args)
        {
            var typeNames = from a in args select GetActualType(a).Name;
            var mname = name + "$" + (typeNames.Count() > 0 ? typeNames.Aggregate((a, b) => a + b) : string.Empty);

            return mname;
        }

        /// <summary>Tries to get a property or field with the given name</summary>
        public override bool TryGetMember(System.Dynamic.GetMemberBinder binder, out object result)
        {
            //Try getting a property of that name
            var prop = WrappedType.GetProperty(binder.Name, FLAGS);

            if (prop == null)
            {
                //Try getting a field of that name
                var fld = WrappedType.GetField(binder.Name, FLAGS);
                if (fld != null)
                {
                    result = fld.GetValue(_wrapped);
                    return true;
                }

                return base.TryGetMember(binder, out result);
            }

            result = prop.GetValue(_wrapped, null);

            return true;
        }

        /// <summary>Tries to set a property or field with the given name</summary>
        public override bool TrySetMember(SetMemberBinder binder, object value)
        {
            var prop = WrappedType.GetProperty(binder.Name, FLAGS);
            if (prop == null)
            {
                var fld = WrappedType.GetField(binder.Name, FLAGS);

                if (fld != null)
                {
                    fld.SetValue(_wrapped, value);
                    return true;
                }

                if (value is Delegate)
                {
                    var param = (value as Delegate).Method.GetParameters();
                    var types = from a in param select GetActualType(a);
                    var method = WrappedType.GetMethod(binder.Name, FLAGS, null, types.ToArray(), null);

                    if (method != null)
                    {
                        string mname = GetInternalName(binder.Name, param);
                        _methods[mname] = value as Delegate;
                        return true;

                    }

                    return base.TrySetMember(binder, value);
                }

                return base.TrySetMember(binder, value);
            }

            prop.SetValue(_wrapped, value, null);

            return true;
        }

    }