Archive for the ‘Evil’ Category

C# 4.0: Exposer, an evil DynamicObject

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 it’s visibility level. Feel free to comment with questions if anything needs explanation ;).

 

Code Snippet
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Dynamic;
  4. using System.Linq;
  5. using System.Linq.Expressions;
  6. using System.Reflection;
  7. using System.Text;
  8.  
  9. namespace Evil
  10. {
  11.     public interface IConvertTo<T>
  12.     {
  13.         T Convert();
  14.     }
  15.  
  16.     public interface IObjectWithType
  17.     {
  18.         Type Type { get; set; }
  19.     }
  20.  
  21.     public class Exposer<T> : DynamicObject, IObjectWithType, IConvertTo<T>
  22.     {
  23.         public T Object { get; set; }
  24.         public Type Type { get; set; }
  25.  
  26.         static Dictionary<string, Func<T, object[], object>> _methods = new Dictionary<string, Func<T, object[], object>>();
  27.         static Dictionary<string, Func<T, object>> _getters = new Dictionary<string, Func<T, object>>();
  28.         static Dictionary<string, Action<T, object>> _setters = new Dictionary<string, Action<T, object>>();
  29.  
  30.         static MethodInfo _doConvert = typeof(Exposer<T>).GetMethod(“DoConvert”, BindingFlags.NonPublic | BindingFlags.Static);
  31.  
  32.         public Exposer(T obj)
  33.         {
  34.             this.Object = obj;
  35.             this.Type = obj.GetType();
  36.         }
  37.  
  38.         public override bool TryGetMember(GetMemberBinder binder, out object result)
  39.         {
  40.             var key = binder.Name;
  41.             Func<T, object> getter = null;
  42.  
  43.             if (_getters.ContainsKey(key))
  44.             {
  45.                 getter = _getters[key];
  46.             }
  47.             else
  48.             {
  49.                 IEnumerable<MemberInfo> members = this.Type.GetMembers(
  50.                     BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.GetProperty | BindingFlags.GetField);
  51.  
  52.                 members = from mem in members
  53.                           where mem.Name == key
  54.                           select mem;
  55.  
  56.                 var member = members.FirstOrDefault();
  57.  
  58.                 if (member != null)
  59.                 {
  60.                     getter = BuildGetter(member);
  61.                     _getters.Add(key, getter);
  62.                 }
  63.             }
  64.  
  65.             if (getter != null)
  66.             {
  67.                 result = Wrap(getter(this.Object));
  68.                 return true;
  69.             }
  70.             else
  71.                 return base.TryGetMember(binder, out result);
  72.         }
  73.  
  74.         public override bool TrySetMember(SetMemberBinder binder, object value)
  75.         {
  76.             var key = binder.Name;
  77.             Action<T, object> setter = null;
  78.  
  79.             if (_setters.ContainsKey(key))
  80.             {
  81.                 setter = _setters[key];
  82.             }
  83.             else
  84.             {
  85.                 IEnumerable<MemberInfo> members = this.Type.GetMembers(
  86.                     BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.SetProperty | BindingFlags.SetField);
  87.  
  88.                 members = from mem in members
  89.                           where mem.Name == key
  90.                           select mem;
  91.  
  92.                 var member = members.FirstOrDefault();
  93.  
  94.                 if (member != null)
  95.                 {
  96.                     setter = BuildSetter(member);
  97.                     _setters.Add(key, setter);
  98.                 }
  99.             }
  100.  
  101.             if (setter != null)
  102.             {
  103.                 setter(this.Object, value);
  104.                 return true;
  105.             }
  106.             else
  107.                 return base.TrySetMember(binder, value);
  108.         }
  109.  
  110.         public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
  111.         {
  112.             Func<T, object[], object> func = null;
  113.  
  114.             var key = MakeKey(binder, args);
  115.  
  116.             if (_methods.ContainsKey(key))
  117.                 func = _methods[key];
  118.             else
  119.             {
  120.                 var argTypes = args.Select(arg => arg is IObjectWithType ? (arg as IObjectWithType).Type : arg.GetType()).ToArray();
  121.                 IEnumerable<MethodInfo> methods = this.Type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
  122.  
  123.                 methods = from method in methods
  124.                           where method.Name == binder.Name && ArgsMatch(method, argTypes)
  125.                           select method;
  126.  
  127.                 var info = methods.FirstOrDefault();
  128.  
  129.                 if (info != null)
  130.                 {
  131.                     var paramTypes = info.GetParameters().Select(p => p.ParameterType).ToArray();
  132.  
  133.                     var target = Expression.Parameter(this.Type, “obj”);
  134.                     var param = Expression.Parameter(typeof(object[]), “args”);
  135.                     var call = Expression.Call(target, info,
  136.                         paramTypes.Select((p, i) =>
  137.                             BuildConvertExpression(Expression.ArrayAccess(param, Expression.Constant(i)), p)));
  138.  
  139.                     func = Expression.Lambda<Func<T, object[], object>>(
  140.                         Expression.Convert(call, typeof(object)), target, param).Compile();
  141.  
  142.  
  143.                 }
  144.  
  145.                 _methods.Add(key, func);
  146.             }
  147.  
  148.             if (func != null)
  149.             {
  150.                 var res = func(this.Object, args);
  151.  
  152.                 result = Wrap(res);
  153.  
  154.                 return true;
  155.             }
  156.             else
  157.                 return base.TryInvokeMember(binder, args, out result);
  158.         }
  159.  
  160.         public override bool TryConvert(ConvertBinder binder, out object result)
  161.         {
  162.             if (binder.Type.IsAssignableFrom(this.Type))
  163.             {
  164.                 result = this.Object;
  165.                 return true;
  166.             }
  167.             else
  168.                 return base.TryConvert(binder, out result);
  169.         }
  170.  
  171.  
  172.  
  173.         #region Builders
  174.         #region Getter
  175.         private Func<T, object> BuildGetter(MemberInfo member)
  176.         {
  177.             switch (member.MemberType)
  178.             {
  179.                 case MemberTypes.Field:
  180.                     return BuildFieldGetter(member as FieldInfo);
  181.                 case MemberTypes.Property:
  182.                     return BuildPropertyGetter(member as PropertyInfo);
  183.                 default:
  184.                     //Returning null effectively marks this as not supported, since the getter will be null as binding exception will be thrown
  185.                     return null;
  186.             }
  187.         }
  188.  
  189.         private Func<T, object> BuildFieldGetter(FieldInfo fieldInfo)
  190.         {
  191.             var param = Expression.Parameter(this.Type, “obj”);
  192.  
  193.             var lambda = Expression.Lambda<Func<T, object>>(
  194.                 Expression.Field(param, fieldInfo),
  195.                 param);
  196.  
  197.             return lambda.Compile();
  198.         }
  199.  
  200.         private Func<T, object> BuildPropertyGetter(PropertyInfo propertyInfo)
  201.         {
  202.             var param = Expression.Parameter(this.Type, “obj”);
  203.  
  204.             var lambda = Expression.Lambda<Func<T, object>>(
  205.                 Expression.Property(param, propertyInfo),
  206.                 param);
  207.  
  208.             return lambda.Compile();
  209.         }
  210.         #endregion
  211.         #region Setter
  212.         private Action<T, object> BuildSetter(MemberInfo member)
  213.         {
  214.             switch (member.MemberType)
  215.             {
  216.                 case MemberTypes.Field:
  217.                     return BuildFieldSetter(member as FieldInfo);
  218.                 case MemberTypes.Property:
  219.                     return BuildPropertySetter(member as PropertyInfo);
  220.                 default:
  221.                     //Returning null effectively marks this as not supported, since the setter will be null as binding exception will be thrown
  222.                     return null;
  223.             }
  224.         }
  225.  
  226.         private Action<T, object> BuildFieldSetter(FieldInfo fieldInfo)
  227.         {
  228.             var param = Expression.Parameter(this.Type, “obj”);
  229.             var value = Expression.Parameter(typeof(object), “val”);
  230.  
  231.             var lambda = Expression.Lambda<Action<T, object>>(
  232.                 Expression.Assign(
  233.                     Expression.Field(param, fieldInfo),
  234.                     Expression.Convert(value, fieldInfo.FieldType)),
  235.                 param, value);
  236.  
  237.             return lambda.Compile();
  238.         }
  239.  
  240.         private Action<T, object> BuildPropertySetter(PropertyInfo propertyInfo)
  241.         {
  242.             var param = Expression.Parameter(this.Type, “obj”);
  243.             var value = Expression.Parameter(typeof(object), “val”);
  244.  
  245.             var lambda = Expression.Lambda<Action<T, object>>(
  246.                 Expression.Assign(
  247.                     Expression.Property(param, propertyInfo),
  248.                     Expression.Convert(value, propertyInfo.PropertyType)),
  249.                 param, value);
  250.  
  251.             return lambda.Compile();
  252.         }
  253.         #endregion
  254.         #region Convert
  255.         public Expression BuildConvertExpression(Expression target, Type type)
  256.         {
  257.             if (type == typeof(object))
  258.                 return target;
  259.  
  260.             return Expression.Call(_doConvert.MakeGenericMethod(type), target);
  261.         }
  262.  
  263.         static R DoConvert<R>(object i)
  264.         {
  265.             if (i is IConvertTo<R>)
  266.             {
  267.                 return (i as IConvertTo<R>).Convert();
  268.             }
  269.             else
  270.             {
  271.                 return (R)i;
  272.             }
  273.         }
  274.         #endregion
  275.         #endregion
  276.  
  277.         #region Helpers
  278.         private static object Wrap(object res)
  279.         {
  280.             if (res == null)
  281.                 return null;
  282.  
  283.             var type = res.GetType();
  284.  
  285.             if (type.IsPrimitive)
  286.                 return res;
  287.  
  288.             var expType = typeof(Exposer<>).MakeGenericType(type);
  289.  
  290.             return Activator.CreateInstance(expType, res);
  291.         }
  292.  
  293.         private static string MakeKey(InvokeMemberBinder binder, object[] args)
  294.         {
  295.             var ret = new StringBuilder();
  296.             ret.Append(binder.Name);
  297.  
  298.             foreach (var arg in args)
  299.                 ret.Append(arg.GetType().Name);
  300.  
  301.             return ret.ToString();
  302.         }
  303.  
  304.         private static bool ArgsMatch(MethodInfo info, Type[] argTypes)
  305.         {
  306.             return info.GetParameters()
  307.                 .Select((p, i) => p.ParameterType.IsAssignableFrom(argTypes[i]))
  308.                 .All(b => b);
  309.         }
  310.         #endregion
  311.  
  312.         #region IConvertTo<T> Members
  313.  
  314.         public T Convert()
  315.         {
  316.             return this.Object;
  317.         }
  318.  
  319.         #endregion
  320.     }
  321.  
  322.     public static class Extensions
  323.     {
  324.         public static Exposer<T> Expose<T>(this T target)
  325.         {
  326.             return new Exposer<T>(target);
  327.         }
  328.     }
  329. }

Advertisements