Click here to Skip to main content
Click here to Skip to main content

C# 4.0: Exposer, an evil DynamicObject

, 8 Oct 2009
Rate this:
Please Sign up or sign in to vote.
This class makes every field, property, or method on the wrapped object visible when using it as a dynamic.

This class makes every field, property, or method on the wrapped object visible when using it as a dynamic. This version is not thread safe, for the sake of brevity I removed all of the locks. To use, you only need to add this to your project, then call .Expose() on an instance of some object, then assign that to a dynamic variable, like so:

dynamic x = someInstance.Expose();

You may then access any field, property, or method regardless of its visibility level. Feel free to comment with questions if anything needs explanation ;).

using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;

namespace Evil
{
    public interface IConvertTo<T>
    {
        T Convert();
    }

    public interface IObjectWithType
    {
        Type Type { get; set; }
    }

    public class Exposer<T> : DynamicObject, IObjectWithType, IConvertTo<T>
    {
        public T Object { get; set; }
        public Type Type { get; set; }

        static Dictionary<string, Func<T, object[], object>> _methods = 
			new Dictionary<string, Func<T, object[], object>>();
        static Dictionary<string, Func<T, object>> _getters = 
				new Dictionary<string, Func<T, object>>();
        static Dictionary<string, Action<T, object>> _setters = 
				new Dictionary<string, Action<T, object>>();

        static MethodInfo _doConvert = typeof(Exposer<T>).GetMethod
		("DoConvert", BindingFlags.NonPublic | BindingFlags.Static);

        public Exposer(T obj)
        {
            this.Object = obj;
            this.Type = obj.GetType();
        }

        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            var key = binder.Name;
            Func<T, object> getter = null;

            if (_getters.ContainsKey(key))
            {
                getter = _getters[key];
            }
            else
            {
                IEnumerable<MemberInfo> members = this.Type.GetMembers(
             	BindingFlags.Instance | BindingFlags.Public | 
		BindingFlags.NonPublic | BindingFlags.GetProperty | 
		BindingFlags.GetField);

                members = from mem in members
                          where mem.Name == key
                          select mem;

                var member = members.FirstOrDefault();

                if(member != null) {
                    getter = BuildGetter(member);
                    _getters.Add(key, getter);
                }
            }

            if (getter != null)
            {
                result = Wrap(getter(this.Object));
                return true;
            }
            else
                return base.TryGetMember(binder, out result);
        }

        public override bool TrySetMember(SetMemberBinder binder, object value)
        {
            var key = binder.Name;
            Action<T, object> setter = null;

            if (_setters.ContainsKey(key))
            {
                setter = _setters[key];
            }
            else
            {
                IEnumerable<MemberInfo> members = this.Type.GetMembers(
             	BindingFlags.Instance | BindingFlags.Public | 
		BindingFlags.NonPublic | BindingFlags.SetProperty | 
		BindingFlags.SetField);

                members = from mem in members
                          where mem.Name == key
                          select mem;

                var member = members.FirstOrDefault();

                if (member != null)
                {
                    setter = BuildSetter(member);
                    _setters.Add(key, setter);
                }
            }

            if (setter != null)
            {
                setter(this.Object, value);
                return true;
            }
            else
                return base.TrySetMember(binder, value);
        }

        public override bool TryInvokeMember
		(InvokeMemberBinder binder, object[] args, out object result)
        {
            Func<T, object[], object> func = null;

            var key = MakeKey(binder, args);

            if (_methods.ContainsKey(key))
                func = _methods[key];
            else
            {
                var argTypes = args.Select(arg => arg is IObjectWithType ? 
			(arg as IObjectWithType).Type : arg.GetType()).ToArray();
                IEnumerable<MethodInfo> methods = this.Type.GetMethods
		(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

                methods = from method in methods
                          where method.Name == binder.Name && ArgsMatch(method, argTypes)
                          select method;

                var info = methods.FirstOrDefault();

                if (info != null)
                {
                    var paramTypes = info.GetParameters().Select
				(p => p.ParameterType).ToArray();

                    var target = Expression.Parameter(this.Type, "obj");
                    var param = Expression.Parameter(typeof(object[]), "args");
                    var call = Expression.Call(target, info,
                        paramTypes.Select((p, i) =>
                            BuildConvertExpression(Expression.ArrayAccess
				(param, Expression.Constant(i)), p)));

                    func = Expression.Lambda<Func<T, object[], object>>(
                        Expression.Convert(call, typeof(object)), 
					target, param).Compile();
                }

                _methods.Add(key, func);
            }

            if (func != null)
            {
                var res = func(this.Object, args);

                result = Wrap(res);

                return true;
            }
            else
                return base.TryInvokeMember(binder, args, out result);
        }

        public override bool TryConvert(ConvertBinder binder, out object result)
        {
            if (binder.Type.IsAssignableFrom(this.Type))
            {
                result = this.Object;
                return true;
            }
            else
                return base.TryConvert(binder, out result);
        }

        #region Builders
        #region Getter
        private Func<T, object> BuildGetter(MemberInfo member)
        {
            switch (member.MemberType)
            {
                case MemberTypes.Field:
                    return BuildFieldGetter(member as FieldInfo);
                case MemberTypes.Property:
                    return BuildPropertyGetter(member as PropertyInfo);
                default:
                    //Returning null effectively marks this as not supported, 
                    //since the getter will be null as binding exception will be thrown
                    return null;
            }
        }

        private Func<T, object> BuildFieldGetter(FieldInfo fieldInfo)
        {
            var param = Expression.Parameter(this.Type, "obj");

            var lambda = Expression.Lambda<Func<T, object>>(
                Expression.Field(param, fieldInfo),
                param);

            return lambda.Compile();
        }

        private Func<T, object> BuildPropertyGetter(PropertyInfo propertyInfo)
        {
            var param = Expression.Parameter(this.Type, "obj");

            var lambda = Expression.Lambda<Func<T, object>>(
                Expression.Property(param, propertyInfo),
                param);

            return lambda.Compile();
        }
        #endregion
        #region Setter
        private Action<T, object> BuildSetter(MemberInfo member)
        {
            switch (member.MemberType)
            {
                case MemberTypes.Field:
                    return BuildFieldSetter(member as FieldInfo);
                case MemberTypes.Property:
                    return BuildPropertySetter(member as PropertyInfo);
                default:
                 	//Returning null effectively marks this as not supported, 
		//since the setter will be null as binding exception will be thrown
                 	return null;
            }
        }

        private Action<T, object> BuildFieldSetter(FieldInfo fieldInfo)
        {
            var param = Expression.Parameter(this.Type, "obj");
            var value = Expression.Parameter(typeof(object), "val");

            var lambda = Expression.Lambda<Action<T, object>>(
                Expression.Assign(
                    Expression.Field(param, fieldInfo),
                    Expression.Convert(value, fieldInfo.FieldType)),
                param, value);

            return lambda.Compile();
        }

        private Action<T, object> BuildPropertySetter(PropertyInfo propertyInfo)
        {
            var param = Expression.Parameter(this.Type, "obj");
            var value = Expression.Parameter(typeof(object), "val");

            var lambda = Expression.Lambda<Action<T, object>>(
                Expression.Assign(
                    Expression.Property(param, propertyInfo),
                    Expression.Convert(value, propertyInfo.PropertyType)),
                param, value);

            return lambda.Compile();
        }
        #endregion
        #region Convert
        public Expression BuildConvertExpression(Expression target, Type type)
        {
            if (type == typeof(object))
                return target;

            return Expression.Call(_doConvert.MakeGenericMethod(type), target);
        }

        static R DoConvert<R>(object i)
        {
            if (i is IConvertTo<R>)
            {
                return (i as IConvertTo<R>).Convert();
            }
            else
            {
                return (R)i;
            }
        }
        #endregion
        #endregion

        #region Helpers
        private static object Wrap(object res)
        {
            if (res == null)
                return null;

            var type = res.GetType();

            if (type.IsPrimitive)
                return res;

            var expType = typeof(Exposer<>).MakeGenericType(type);

            return Activator.CreateInstance(expType, res);
        }

        private static string MakeKey(InvokeMemberBinder binder, object[] args)
        {
            var ret = new StringBuilder();
            ret.Append(binder.Name);

            foreach (var arg in args)
                ret.Append(arg.GetType().Name);

            return ret.ToString();
        }

        private static bool ArgsMatch(MethodInfo info, Type[] argTypes)
        {
            return info.GetParameters()
                .Select((p, i) => p.ParameterType.IsAssignableFrom(argTypes[i]))
                .All(b => b);
        }
        #endregion

        #region IConvertTo<T> Members

        public T Convert()
        {
            return this.Object;
        }

        #endregion
    }

    public static class Extensions
    {
        public static Exposer<T> Expose<T>(this T target)
        {
            return new Exposer<T>(target);
        }
    }
}

License

This article, along with any associated source code and files, is licensed under The MIT License

About the Author

StormySpike
Software Developer
United States United States
I currently work as a Software Engineer for a company in North Carolina, mainly working with C#.

Comments and Discussions

 
-- There are no messages in this forum --
| Advertise | Privacy | Mobile
Web02 | 2.8.140721.1 | Last Updated 8 Oct 2009
Article Copyright 2009 by StormySpike
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid