public static Func <object, object[], object> DynamicMethodBindCall(MethodInfo method) { var type = method.DeclaringType; if (type == null) { throw new NotSupportedException("Dynamic binding does not currently support anonymous methods"); } var restrictedSkipVisibility = type.IsNotPublic; var dm = new DynamicMethod($"Call_{method.MetadataToken}", typeof(object), new[] { typeof(object), typeof(object[]) }, restrictedSkipVisibility); dm.GetILGeneratorInternal().EmitCallDelegate(type, method); return((Func <object, object[], object>)dm.CreateDelegate(typeof(Func <object, object[], object>))); }
public static CreateInstance DynamicMethodWeakTyped(ConstructorInfo ctor) { var dm = new DynamicMethod($"Construct__{ctor.DeclaringType?.Assembly.GetHashCode()}_{ctor.MetadataToken}", ctor.DeclaringType, new[] { typeof(object[]) }); var il = dm.GetILGeneratorInternal(); var parameters = ctor.GetParameters(); for (var i = 0; i < parameters.Length; i++) { il.LoadArgument(i); il.LoadConstant(i); il.Ldelem_Ref(); il.CastOrUnboxAny(parameters[i].ParameterType); } il.Newobj(ctor); il.Ret(); return((CreateInstance)dm.CreateDelegate(typeof(CreateInstance))); }
/// <summary> /// Anonymous types only have private readonly properties with no logic before their backing fields, so we can do /// a lot to optimize access to them, though we must delegate the access itself due to private reflection rules. /// </summary> private static ITypeReadAccessor CreateAnonymousReadAccessor(Type type, out AccessorMembers members, object debugObject = null) { members = CreateAnonymousReadAccessorMembers(type); var name = type.CreateNameForReadAccessor(members.Types, members.Scope); var tb = DynamicAssembly.Module.DefineType(name, TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.BeforeFieldInit | TypeAttributes.AutoClass | TypeAttributes.AnsiClass); tb.AddInterfaceImplementation(typeof(ITypeReadAccessor)); // // Perf: Add static delegates on the type, that store access to the backing fields behind readonly properties. // var staticFieldsByMethod = new Dictionary <MethodBuilder, Func <object, object> >(); var staticFieldsByMember = new Dictionary <AccessorMember, FieldBuilder>(); foreach (var member in members) { var backingField = type.GetField($"<{member.Name}>i__Field", BindingFlags.NonPublic | BindingFlags.Instance); if (backingField == null) { throw new NullReferenceException(); } var dm = new DynamicMethod($"_{member.Name}", typeof(object), new[] { typeof(object) }, tb.Module); var dmIl = dm.GetILGeneratorInternal(); dmIl.Ldarg_0() .Ldfld(backingField); if (backingField.FieldType.IsValueType) { dmIl.Box(backingField.FieldType); } dmIl.Ret(); var backingFieldDelegate = (Func <object, object>)dm.CreateDelegate(typeof(Func <object, object>)); var getField = tb.DefineField($"_Get{member.Name}", typeof(Func <object, object>), FieldAttributes.Public | FieldAttributes.Static | FieldAttributes.InitOnly); var setField = tb.DefineMethod($"_SetGet{member.Name}", MethodAttributes.Static | MethodAttributes.Private, CallingConventions.Standard, typeof(void), new[] { typeof(Func <object, object>) }); var setFieldIl = setField.GetILGeneratorInternal(); setFieldIl.Ldarg_0(); setFieldIl.Stsfld(getField); setFieldIl.Ret(); staticFieldsByMethod.Add(setField, backingFieldDelegate); staticFieldsByMember.Add(member, getField); } // // Type Type =>: // { tb.MemberProperty(nameof(ITypeReadAccessor.Type), type, typeof(ITypeReadAccessor).GetMethod($"get_{nameof(ITypeReadAccessor.Type)}")); } // // bool TryGetValue(object target, string key, out object value): // { var tryGetValue = tb.DefineMethod(nameof(ITypeReadAccessor.TryGetValue), MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.NewSlot, typeof(bool), new[] { typeof(object), typeof(string), typeof(object).MakeByRefType() }); var il = tryGetValue.GetILGeneratorInternal(); var branches = new Dictionary <AccessorMember, Label>(); foreach (var member in members) { branches.Add(member, il.DefineLabel()); } var fail = il.DefineLabel(); foreach (var member in members) { il.Ldarg_2(); // key il.GotoIfStringEquals(member.Name, branches[member]); // if(key == "Foo") goto found; } il.Br(fail); foreach (var member in members) { var fb = staticFieldsByMember[member]; il.MarkLabel(branches[member]); // found: il.Ldarg_3(); // value il.Ldsfld(fb); // _GetFoo il.Ldarg_1(); // target il.Call(fb.FieldType.GetMethod("Invoke")); // result = _GetFoo.Invoke(target) il.Stind_Ref(); // value = result il.Ldc_I4_1(); // 1 il.Ret(); // return 1 (true) } il.MarkLabel(fail); il.Ldarg_3(); // value il.Ldnull(); // null il.Stind_Ref(); // value = null il.Ldc_I4_0(); // 0 il.Ret(); // return 0 (false) tb.DefineMethodOverride(tryGetValue, typeof(IReadAccessor).GetMethod(nameof(IReadAccessor.TryGetValue))); } // // object this[object target, string key]: // { var getItem = tb.DefineMethod("get_Item", MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.NewSlot | MethodAttributes.SpecialName, typeof(object), new[] { typeof(object), typeof(string) }); var il = getItem.GetILGeneratorInternal(); var branches = new Dictionary <AccessorMember, Label>(); foreach (var member in members) { branches.Add(member, il.DefineLabel()); } foreach (var member in members) { il.Ldarg_2(); // key il.GotoIfStringEquals(member.Name, branches[member]); // if(key == "Foo") goto found; } foreach (var member in members) { var fb = staticFieldsByMember[member]; il.MarkLabel(branches[member]); // found: il.Ldsfld(fb); // _GetFoo il.Ldarg_1(); // target il.Call(fb.FieldType.GetMethod("Invoke")); // result = _GetFoo.Invoke(target) il.Ret(); // return result; } il.Newobj(typeof(ArgumentNullException).GetConstructor(Type.EmptyTypes)); il.Throw(); var item = tb.DefineProperty("Item", PropertyAttributes.SpecialName, typeof(object), new[] { typeof(string) }); item.SetGetMethod(getItem); tb.DefineMethodOverride(getItem, typeof(IReadAccessor).GetMethod("get_Item")); } var typeInfo = tb.CreateTypeInfo(); // // Perf: Set static field values to generated delegate instances. // foreach (var setter in staticFieldsByMethod) { var setField = typeInfo.GetMethod(setter.Key.Name, BindingFlags.Static | BindingFlags.NonPublic); if (setField == null) { throw new NullReferenceException(); } setField.Invoke(null, new object[] { setter.Value }); if (debugObject != null) { var memberName = setter.Key.Name.Replace("_SetGet", string.Empty); var staticFieldFunc = (Func <object, object>)typeInfo.GetField($"_Get{memberName}").GetValue(debugObject); if (staticFieldFunc != setter.Value) { throw new ArgumentException( $"replacing _Get{memberName} with function from _SetGet{memberName} was unsuccessful"); } var backingField = type.GetField($"<{memberName}>i__Field", BindingFlags.NonPublic | BindingFlags.Instance); if (backingField == null) { throw new NullReferenceException("backing field was not found"); } var backingFieldValue = backingField.GetValue(debugObject); var cachedDelegateValue = setter.Value(debugObject); if (backingFieldValue != null && !backingFieldValue.Equals(cachedDelegateValue)) { throw new ArgumentException( $"{memberName} backing field value '{backingFieldValue}' does not agree with cached delegate value {cachedDelegateValue}"); } } } var accessor = (ITypeReadAccessor)Activator.CreateInstance(typeInfo, false); if (debugObject != null) { foreach (var member in members) { var byAccessor = accessor[debugObject, member.Name]; var byReflection = ((Func <object, object>)typeInfo.GetField($"_Get{member.Name}").GetValue(debugObject))( debugObject); if (byAccessor != null && !byAccessor.Equals(byReflection)) { throw new InvalidOperationException("IL produced incorrect accessor"); } } } return(accessor); }
private static ITypeWriteAccessor CreateWriteAccessor(Type type, AccessorMemberTypes types, AccessorMemberScope scope, out AccessorMembers members) { members = CreateWriteAccessorMembers(type, types, scope); if (type.NeedsLateBoundAccessor(members)) { return(new LateBoundTypeWriteAccessor(members)); } var name = type.CreateNameForWriteAccessor(members.Types, members.Scope); var tb = DynamicAssembly.Module.DefineType(name, TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.BeforeFieldInit | TypeAttributes.AutoClass | TypeAttributes.AnsiClass); tb.AddInterfaceImplementation(typeof(ITypeWriteAccessor)); // // Generate proxies for all backing field setters we will need for this type // var setFieldDelegateMethods = new Dictionary <MethodBuilder, Delegate>(); var getFieldDelegateMethods = new List <MethodBuilder>(); var invokeFieldDelegateMethods = new List <MethodBuilder>(); var callSwaps = new Dictionary <AccessorMember, MethodInfo>(); foreach (var member in members) { if (!member.CanWrite) { continue; // can't write / is a computed property } if (!(member.MemberInfo is PropertyInfo property)) { continue; // not a property } if (property.GetSetMethod(true) != null) { continue; // has public setter } var backingField = member.BackingField; if (backingField == null) { continue; } var setFieldMethod = new DynamicMethod($"set_{member.Name}", typeof(void), new[] { backingField.DeclaringType, backingField.FieldType }, backingField.DeclaringType, true); var setFieldIl = setFieldMethod.GetILGeneratorInternal(); setFieldIl.Ldarg_0(); setFieldIl.Ldarg_1(); setFieldIl.Stfld(backingField); setFieldIl.Ret(); var setFieldDelegateType = typeof(Action <,>).MakeGenericType(backingField.DeclaringType, backingField.FieldType); var setFieldDelegate = setFieldMethod.CreateDelegate(setFieldDelegateType); // At this point we have a valid delegate that will set the private backing field... var setFieldDelegateField = tb.DefineField( $"_setFieldDelegate_{member.Type.Namespace}_{member.Type.Name}_{backingField.Name}", setFieldDelegateType, FieldAttributes.Private | FieldAttributes.Static | FieldAttributes.InitOnly); var setFieldDelegateMethod = tb.DefineMethod( $"_set_setFieldDelegate_{member.Type.Namespace}_{member.Type.Name}_{backingField.Name}", MethodAttributes.Private | MethodAttributes.Static, CallingConventions.Standard, typeof(void), new[] { setFieldDelegateType }); var setFieldDelegateIl = setFieldDelegateMethod.GetILGeneratorInternal(); setFieldDelegateIl.Ldarg_0(); setFieldDelegateIl.Stsfld(setFieldDelegateField); setFieldDelegateIl.Ret(); setFieldDelegateMethods.Add(setFieldDelegateMethod, setFieldDelegate); // At this point we've defined a static field to store our delegate setter and a method to set that field... var getFieldDelegateMethod = tb.DefineMethod( $"_get_setFieldDelegate_{member.Type.Namespace}_{member.Type.Name}_{backingField.Name}", MethodAttributes.Private | MethodAttributes.Static, CallingConventions.Standard, setFieldDelegateType, null); var getFieldDelegateIl = getFieldDelegateMethod.GetILGeneratorInternal(); getFieldDelegateIl.Ldsfld(setFieldDelegateField); getFieldDelegateIl.Ret(); getFieldDelegateMethods.Add(getFieldDelegateMethod); // At this point we can get and set the static field delegates, and now we need to cache a method to invoke them... var invokeFieldDelegateMethod = tb.DefineMethod( $"_invoke_setFieldDelegate_{member.Type.Namespace}_{member.Type.Name}_{backingField.Name}", MethodAttributes.Static | MethodAttributes.Private, CallingConventions.Standard, typeof(void), new[] { backingField.DeclaringType, backingField.FieldType }); var invokeFieldDelegateIl = invokeFieldDelegateMethod.GetILGenerator(); invokeFieldDelegateIl.Emit(OpCodes.Ldsfld, setFieldDelegateField); invokeFieldDelegateIl.Emit(OpCodes.Ldarg_0); invokeFieldDelegateIl.Emit(OpCodes.Ldarg_1); invokeFieldDelegateIl.Emit(OpCodes.Callvirt, setFieldDelegateType.GetMethod("Invoke")); invokeFieldDelegateIl.Emit(OpCodes.Ret); invokeFieldDelegateMethods.Add(invokeFieldDelegateMethod); callSwaps.Add(member, invokeFieldDelegateMethod); } // // Type Type =>: // { var getType = tb.DefineMethod($"get_{nameof(ITypeWriteAccessor.Type)}", MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.NewSlot | MethodAttributes.SpecialName, typeof(Type), Type.EmptyTypes); var il = getType.GetILGeneratorInternal(); il.Ldtoken(type); il.CallOrCallvirt(type, typeof(Type).GetMethod(nameof(Type.GetTypeFromHandle), BindingFlags.Static | BindingFlags.Public)); il.Ret(); var getTypeProperty = tb.DefineProperty(nameof(ITypeWriteAccessor.Type), PropertyAttributes.None, typeof(object), new[] { typeof(string) }); getTypeProperty.SetGetMethod(getType); tb.DefineMethodOverride(getType, typeof(ITypeWriteAccessor).GetMethod($"get_{nameof(ITypeWriteAccessor.Type)}")); } // // bool TrySetValue(object target, string key, object value): // { var trySetValue = tb.DefineMethod(nameof(IWriteAccessor.TrySetValue), MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.NewSlot, typeof(bool), new[] { typeof(object), typeof(string), typeof(object) }); var il = trySetValue.GetILGeneratorInternal(); var branches = new Dictionary <AccessorMember, Label>(); foreach (var member in members) { if (!member.CanWrite) { continue; } branches.Add(member, il.DefineLabel()); } foreach (var member in members) { if (!member.CanWrite) { continue; } il.Ldarg_2(); // key il.GotoIfStringEquals(member.Name, branches[member]); // if (key == "{member.Name}") goto found; } foreach (var member in members) { if (!member.CanWrite) { continue; } il.MarkLabel(branches[member]); // found: il.Ldarg_1(); // target il.CastOrUnbox(type); // ({Type}) target il.Ldarg_3(); // value switch (member.MemberInfo) // target.{member.Name} = value { case PropertyInfo property: il.CastOrUnboxAny(property.PropertyType); il.CallOrCallvirt(type, GetOrSwapPropertySetter(property, callSwaps, member)); break; case FieldInfo field: il.CastOrUnboxAny(field.FieldType); il.Stfld(field); break; } il.Ldc_I4_1(); // 1 il.Ret(); // return 1 (true) } il.Ldnull(); // null il.Starg_S(); // value = null il.Ldc_I4_0(); // 0 il.Ret(); // return 0 (false) tb.DefineMethodOverride(trySetValue, typeof(IWriteAccessor).GetMethod(nameof(IWriteAccessor.TrySetValue))); } // // object this[object target, string key] = object value: // { var setItem = tb.DefineMethod("set_Item", MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.NewSlot | MethodAttributes.SpecialName, typeof(void), new[] { typeof(object), typeof(string), typeof(object) }); var il = setItem.GetILGeneratorInternal(); var branches = new Dictionary <AccessorMember, Label>(); foreach (var member in members) { if (!member.CanWrite) { continue; } branches.Add(member, il.DefineLabel()); } foreach (var member in members) { if (!member.CanWrite) { continue; } il.Ldarg_2(); // key il.GotoIfStringEquals(member.Name, branches[member]); // if (key == "{member.Name}") goto found; } foreach (var member in members) { if (!member.CanWrite) { continue; } il.MarkLabel(branches[member]); // found: il.Ldarg_1(); // target il.CastOrUnbox(type); // ({Type}) target il.Ldarg_3(); // value switch (member.MemberInfo) // result = target.{member.Name} { case PropertyInfo property: il.CastOrUnboxAny(property.PropertyType); il.CallOrCallvirt(type, GetOrSwapPropertySetter(property, callSwaps, member)); break; case FieldInfo field: il.CastOrUnboxAny(field.FieldType); il.Stfld(field); break; } il.Ret(); // return result; } il.Newobj(typeof(ArgumentNullException).GetConstructor(Type.EmptyTypes)).Throw(); var item = tb.DefineProperty("Item", PropertyAttributes.SpecialName, typeof(object), new[] { typeof(string) }); item.SetSetMethod(setItem); tb.DefineMethodOverride(setItem, typeof(IWriteAccessor).GetMethod("set_Item")); } var typeInfo = tb.CreateTypeInfo(); // Now we need to set the static delegate fields on this type... foreach (var entry in setFieldDelegateMethods) { var setFieldMethod = typeInfo.GetMethod(entry.Key.Name, BindingFlags.Static | BindingFlags.NonPublic); if (setFieldMethod == null) { continue; } setFieldMethod.Invoke(null, new object[] { entry.Value }); } // Test that we can access the static fields correctly... foreach (var entry in getFieldDelegateMethods) { var getFieldMethod = typeInfo.GetMethod(entry.Name, BindingFlags.Static | BindingFlags.NonPublic); if (getFieldMethod == null) { continue; } var field = getFieldMethod.Invoke(null, null); } // Finally, test that we can invoke the delegate and set a backing field foreach (var entry in invokeFieldDelegateMethods) { if (entry.Name != "_invoke_setFieldDelegate_System_String_<Id>k__BackingField") { continue; } var invokeFieldMethod = typeInfo.GetMethod(entry.Name, BindingFlags.Static | BindingFlags.NonPublic); if (invokeFieldMethod == null) { throw new NullReferenceException(); } var test = Activator.CreateInstance(type); invokeFieldMethod.Invoke(null, new[] { test, "Foo" }); } return((ITypeWriteAccessor)Activator.CreateInstance(typeInfo.AsType(), false)); }