Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

C# 4.0: Exposer, an evil DynamicObject

0.00/5 (No votes)
8 Oct 2009 1  
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:

C#
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 ;).

C#
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 has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here