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
- 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);
- }
- }
- }
Advertisement