Created
March 12, 2014 17:25
-
-
Save default-kramer/9511855 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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