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