Skip to content

Instantly share code, notes, and snippets.

@default-kramer
Created March 12, 2014 17:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save default-kramer/9511855 to your computer and use it in GitHub Desktop.
Save default-kramer/9511855 to your computer and use it in GitHub Desktop.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Reflection;
using System.Reflection.Emit;
using System.Linq.Expressions;
namespace Mixer.Tests
{
[TestClass]
public class MyTestClass
{
public interface IMixinBuilder
{
TypeBuilder TypeBuilder { get; }
FieldBuilder WrappedField { get; }
}
public class MixinBuilder<TWrapped, TOuter, TArgs> : IMixinBuilder
{
static readonly ConstructorInfo objectCtor = typeof(object).GetConstructor(Type.EmptyTypes);
protected MixinBuilder() { }
protected virtual void Customize(BuildOptions<TWrapped, TOuter> options)
{
}
public TOuter Convert(TWrapped instance, TArgs args)
{
Type type = BuildType();
object o = Activator.CreateInstance(type, instance, args);
return (TOuter)o;
}
private BuildOptions<TWrapped, TOuter> options;
private Type type;
private Type BuildType()
{
if (type != null) return type;
options = new BuildOptions<TWrapped, TOuter>();
Customize(options);
BuildCtor();
var tb = TypeBuilder;
foreach (var method in typeof(TOuter).GetMethods())
{
BuildMethod(method);
}
tb.AddInterfaceImplementation(typeof(TOuter));
type = TypeBuilder.CreateType();
return type;
}
private void BuildMethod(MethodInfo method)
{
MethodBuilder b = options.Handle(method, this)
?? TryFindStaticOverride(method)
?? TryBuildDirectWrapper(method);
if (b == null)
{
throw new Exception("TODO couldn't handle it");
}
TypeBuilder.DefineMethodOverride(b, method);
}
private IEnumerable<Type> InnerTypes()
{
return new[] { typeof(TWrapped) }
.SelectMany(x => new[] { x }.Concat(_childTypes(x)))
.Distinct();
}
private IEnumerable<Type> _childTypes(Type parent)
{
return parent.GetInterfaces().Concat(new[] { parent.BaseType })
.Where(x => x != null);
}
private static bool ParametersMatch(MethodInfo first, MethodInfo second)
{
return first.GetParameters().Select(p => p.ParameterType).SequenceEqual(
second.GetParameters().Select(p => p.ParameterType));
}
private MethodBuilder TryBuildDirectWrapper(MethodInfo method)
{
var metchingMethod = InnerTypes()
.SelectMany(t => t.GetMethods(BindingFlags.Instance | BindingFlags.Public))
.Where(x => x.Name == method.Name)
.Where(x => x.ReturnType == method.ReturnType)
.Where(x => ParametersMatch(x, method))
.FirstOrDefault();
if (metchingMethod == null)
{
return null;
}
var atts = MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.Final;
if (method.IsSpecialName)
{
atts |= MethodAttributes.SpecialName;
}
string methodName = method.DeclaringType + "." + method.Name;
var wrapper = TypeBuilder.DefineMethod(methodName, atts,
method.ReturnType, method.GetParameters().Select(p => p.ParameterType).ToArray());
var il = wrapper.GetILGenerator();
int ldargCount = 0;
LoadWrapped(il);
ldargCount++;
foreach (var parm in method.GetParameters())
{
il.Emit(OpCodes.Ldarg, ldargCount++);
}
il.Emit(OpCodes.Call, metchingMethod);
il.Emit(OpCodes.Ret);
return wrapper;
}
private MethodBuilder TryFindStaticOverride(MethodInfo method)
{
var parms = method.GetParameters();
var impl = this.GetType()
.GetMethods(BindingFlags.Public | BindingFlags.Static)
.Where(x => x.Name == method.Name)
.Where(x => x.GetParameters().Skip(1).Select(p => p.ParameterType)
.SequenceEqual(parms.Select(p => p.ParameterType)))
.SingleOrDefault();
if (impl == null)
{
return null;
}
var wrapper = TypeBuilder.DefineMethod(method.Name,
MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.Final,
method.ReturnType, parms.Select(p => p.ParameterType).ToArray());
var il = wrapper.GetILGenerator();
LoadWrapped(il);
for (int i = 1; i <= parms.Length; i++)
{
il.Emit(OpCodes.Ldarg, i);
}
il.Emit(OpCodes.Call, impl);
il.Emit(OpCodes.Ret);
return wrapper;
}
public void LoadWrapped(ILGenerator il)
{
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, WrappedField);
}
private FieldBuilder _wrappedField;
public FieldBuilder WrappedField
{
get
{
_wrappedField = _wrappedField ?? TypeBuilder.DefineField("wrapped", typeof(TWrapped), FieldAttributes.Public | FieldAttributes.InitOnly);
return _wrappedField;
}
}
private FieldBuilder _argsField;
private FieldBuilder ArgsField
{
get
{
_argsField = _argsField ?? TypeBuilder.DefineField("args", typeof(TArgs), FieldAttributes.Public | FieldAttributes.InitOnly);
return _argsField;
}
}
private void BuildCtor()
{
var tb = TypeBuilder;
var ctor = tb.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, new[] { typeof(TWrapped), typeof(TArgs) });
ctor.DefineParameter(0, ParameterAttributes.In, "this");
ctor.DefineParameter(1, ParameterAttributes.In, "wrapped");
ctor.DefineParameter(2, ParameterAttributes.In, "args");
var il = ctor.GetILGenerator();
// : base()
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Call, objectCtor);
// this.wrapped = wrapped
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Stfld, WrappedField);
// this.args = args
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_2);
il.Emit(OpCodes.Stfld, ArgsField);
il.Emit(OpCodes.Ret);
}
private TypeBuilder _tb;
public TypeBuilder TypeBuilder
{
get
{
_tb = _tb ?? CreateTypeBuilder();
return _tb;
}
}
protected virtual TypeBuilder CreateTypeBuilder()
{
var typeSignature = this.GetType().Name + "__";
var an = new AssemblyName(typeSignature);
AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run);
ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("MainModule");
TypeBuilder tb = moduleBuilder.DefineType(typeSignature
, TypeAttributes.Public
| TypeAttributes.Class
//| TypeAttributes.AutoClass |
//TypeAttributes.AnsiClass |
//TypeAttributes.BeforeFieldInit |
//TypeAttributes.AutoLayout
);
return tb;
}
}
public interface IMixinHandler
{
MethodBuilder Handle(MethodInfo parentMethod, IMixinBuilder builder);
}
public class BuildOptions<TWrapped, TOuter> : IMixinHandler
{
private List<IMixinHandler> handlers = new List<IMixinHandler>();
public PropertyDefiner<TWrapped, TRetval> Implement<TRetval>(Expression<Func<TOuter, TRetval>> method)
{
var x = (MemberExpression)(method.Body);
var prop = (PropertyInfo)x.Member;
var bt = new PropertyDefiner<TWrapped, TRetval>(prop);
handlers.Add(bt);
return bt;
}
public MethodBuilder Handle(MethodInfo parentMethod, IMixinBuilder builder)
{
return handlers.Select(h => h.Handle(parentMethod, builder))
.Where(x => x != null)
.FirstOrDefault();
}
}
public class PropertyDefiner<TWrapped, TPropType> : IMixinHandler
{
private readonly PropertyInfo prop;
private Expression<Func<TWrapped, TPropType>> getter;
private Expression<Action<TWrapped, TPropType>> setter;
public PropertyDefiner(PropertyInfo prop)
{
this.prop = prop;
}
public PropertyDefiner<TWrapped, TPropType> Getter(Expression<Func<TWrapped, TPropType>> getter)
{
this.getter = getter;
return this;
}
public PropertyDefiner<TWrapped, TPropType> Setter(Expression<Action<TWrapped, TPropType>> setter)
{
this.setter = setter;
return this;
}
// TODO aren't these just naming conventions? there must a more reliable way to figure out if this is
// the backing method for a property
private bool IsGetter(MethodInfo method)
{
return method.Name == "get_" + prop.Name;
}
private bool IsSetter(MethodInfo method)
{
return method.Name == "set_" + prop.Name;
}
public MethodBuilder Handle(MethodInfo method, IMixinBuilder builder)
{
if (method.DeclaringType != prop.DeclaringType)
{
return null;
}
return BuildGetter(method, builder)
?? BuildSetter(method, builder);
}
private MethodBuilder BuildSetter(MethodInfo method, IMixinBuilder builder)
{
if (!IsSetter(method) || setter == null)
{
return null;
}
var tb = builder.TypeBuilder;
Type returnType = null;
var paramTypes = new[] { typeof(TPropType) };
var lambda = tb.DefineMethod(method.Name + "_setter",
MethodAttributes.Final | MethodAttributes.Static | MethodAttributes.Private,
returnType, paramTypes);
setter.CompileToMethod(lambda);
var wrapper = tb.DefineMethod(method.Name,
MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.Final,
returnType, paramTypes);
var il = wrapper.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, builder.WrappedField);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Call, lambda);
il.Emit(OpCodes.Ret);
return wrapper;
}
private MethodBuilder BuildGetter(MethodInfo method, IMixinBuilder builder)
{
if (!IsGetter(method) || getter == null)
{
return null;
}
var tb = builder.TypeBuilder;
var lambda = tb.DefineMethod(method.Name + "_getter",
MethodAttributes.Final | MethodAttributes.Static | MethodAttributes.Private,
method.ReturnType, Type.EmptyTypes);
getter.CompileToMethod(lambda);
var wrapper = tb.DefineMethod(method.Name,
MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.Final,
method.ReturnType, method.GetParameters().Select(p => p.ParameterType).ToArray());
var il = wrapper.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, builder.WrappedField);
il.Emit(OpCodes.Call, lambda);
il.Emit(OpCodes.Ret);
return wrapper;
}
}
public interface I
{
int PassThrough { get; }
int Augmented { get; }
string Settable { get; set; }
int Divide(int i, int j);
}
public interface IExtended
{
string ExtendedProp { get; }
}
public class C : I
{
public int PassThrough { get { return 4; } }
public int Augmented { get { return 99; } }
public string Settable { get; set; }
public int Divide(int i, int j) { return i / j; }
}
public class Mixer : MixinBuilder<I, I, object>
{
public static readonly Mixer Instance = new Mixer();
protected override void Customize(BuildOptions<I, I> options)
{
options.Implement(outer => outer.Augmented)
.Getter(inner => inner.Augmented + 2);
options.Implement(outer => outer.Settable)
.Getter(inner => "%" + inner.Settable + "%")
;//.Setter((i, s) => Set_Settable(i, s));
}
public static void set_Settable(I instance, string value)
{
instance.Settable = "<" + value + ">";
}
public static int DivideDelta = 0;
public static int Divide(I inner, int i, int j)
{
return inner.Divide(i, j) + DivideDelta;
}
}
public class I_to_IExtended : MixinBuilder<I, IExtended, object>
{
public static readonly I_to_IExtended Instance = new I_to_IExtended();
public static string get_ExtendedProp(I instance)
{
return "from the mixin";
}
}
[TestMethod]
public void can_do_it()
{
var c = new C();
I i = Mixer.Instance.Convert(c, new object());
Assert.IsNotNull(i);
Assert.AreEqual(4, i.PassThrough);
Assert.AreEqual(101, i.Augmented);
Assert.AreEqual("%%", i.Settable);
i.Settable = "hello";
Assert.AreEqual("%<hello>%", i.Settable);
Assert.AreEqual(5, i.Divide(20, 4));
Mixer.DivideDelta = 3;
Assert.AreEqual(8, i.Divide(20, 4));
var extended = I_to_IExtended.Instance.Convert(i, new object());
Assert.AreEqual("from the mixin", extended.ExtendedProp);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment