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>)));
        }
Exemple #2
0
        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)));
        }
Exemple #3
0
        /// <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);
        }
Exemple #4
0
        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));
        }