Loading a ParameterInfo using IL Emit

微笑、不失礼 提交于 2020-01-15 08:55:12

问题


I am currently using Calling a method of existing object using IL Emit as a guide, and I can already do whatever is asked in question. Now, I have an attribute added to a parameter and I want to load that particular parameter's attribute so that I can call a method inside that attribute.

I know it can be done by loading the MethodInfo and then getting the ParameterInfo and then getting attribute of that ParameterInfo in IL; I am simply trying to avoid writing that much IL.

Is there a way to load a parameter's attribute in IL just like it is mentioned in the linked post?

Edit: I have a method with a signature like

Method([Attr] int Parameter)

and I want to load a method which is part of the Attr. I was just hoping I could load ParameterInfo (obtained using MethodInfo.GetParameters()) directly onto the stack. Turns out, LdToken doesn't really allow putting ParameterInfo. Only other way I can think of doing this is to load MethodInfo (LdToken supports that) and then use GetParameters() in IL to get an array of Parameters and then loop through them in IL one by one to get each one's Attribute (using .GetCustomAttribute(Type)) and then call the method on that attribute. Note that I don't have to get a field of an attribute, I need to call a method on that attribute.


回答1:


K, third time lucky based on another interpretation of the question; here, we're assuming that we want to invoke methods on an attribute instance. We need to consider that attributes only kinda sorta exist at runtime - we can create synthetic instances of the attribute as represented by the metadata, but this isn't particularly cheap or fast, so we should ideally only do this once (the metadata isn't going to change, after all). This means we might want to store the instance as a field somewhere. This could be an instance field, or a static field - in many cases, a static field is fine. Consider:

using System;
using System.Reflection;
using System.Reflection.Emit;

public class SomethingAttribute : Attribute
{
    public SomethingAttribute(string name)
        => Name = name;
    public string Name { get; }

    public void SomeMethod(int i)
    {
        Console.WriteLine($"SomeMethod: {Name}, {i}");
    }
}
public static class P
{
    public static void Foo([Something("Abc")] int x)
    {
        Console.WriteLine($"Foo: {x}");
    }

    public static void Main()
    {
        // get the attribute
        var method = typeof(P).GetMethod(nameof(Foo));
        var p = method.GetParameters()[0];
        var attr = (SomethingAttribute)Attribute.GetCustomAttribute(p, typeof(SomethingAttribute));

        // define an assembly, module and type to play with
        AssemblyBuilder asm = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("Evil"), AssemblyBuilderAccess.Run);
        var module = asm.DefineDynamicModule("Evil");
        var type = module.DefineType("SomeType", TypeAttributes.Public);

        // define a field where we'll store our synthesized attribute instance; avoid initonly, unless you're
        // going to write code in the .cctor to initialize it; leaving it writable allows us to assign it via
        // reflection
        var attrField = type.DefineField("s_attr", typeof(SomethingAttribute), FieldAttributes.Static | FieldAttributes.Private);

        // declare the method we're working on
        var bar = type.DefineMethod("Bar", MethodAttributes.Static | MethodAttributes.Public, typeof(void), new[] { typeof(int) });
        var il = bar.GetILGenerator();

        // use the static field instance as our target to invoke the attribute method
        il.Emit(OpCodes.Ldsfld, attrField); // the attribute instance
        il.Emit(OpCodes.Ldarg_0); // the integer
        il.EmitCall(OpCodes.Callvirt, typeof(SomethingAttribute).GetMethod(nameof(SomethingAttribute.SomeMethod)), null);
        // and also call foo
        il.Emit(OpCodes.Ldarg_0); // the integer
        il.EmitCall(OpCodes.Call, typeof(P).GetMethod(nameof(P.Foo)), null);

        il.Emit(OpCodes.Ret);

        // complete the type
        var actualType = type.CreateType();
        // assign the synthetic attribute instance on the concrete type
        actualType.GetField(attrField.Name, BindingFlags.Static | BindingFlags.NonPublic).SetValue(null, attr);

        // get a delegate to the method
        var func = (Action<int>)Delegate.CreateDelegate(typeof(Action<int>), actualType.GetMethod(bar.Name));
        // and test it
        for (int i = 0; i < 5; i++)
            func(i);
    }
}

Output from the final loop (for (int i = 0; i < 5; i++) func(i);):

SomeMethod: Abc, 0
Foo: 0
SomeMethod: Abc, 1
Foo: 1
SomeMethod: Abc, 2
Foo: 2
SomeMethod: Abc, 3
Foo: 3
SomeMethod: Abc, 4
Foo: 4

As a side note; in many ways it is easier to do this with expression-trees, since expression-trees have Expression.Constant which can be the attribute instance, and which is treated like a field internally. But you mentioned TypeBuilder, so I went this way :)




回答2:


I know it can be done by loading the MethodInfo and then getting the ParameterInfo and then getting attribute of that ParameterInfo in IL; I am simply trying to avoid writing that much IL.

Yeah, that's pretty much it, in IL; IL is powerful, but is not particularly terse or simple. Just like in the linked post, you'd end up loading the parameter (ldarg or ldarga, maybe some .s), then depending on whether the member is a field or a property, using either ldfld or callvirt on the property getter. About 3 lines, so not huge; perhaps something like:

static void EmitLoadPropertyOrField(ILGenerator il, Type type, string name)
{
    // assumes that the target *reference*  has already been loaded; only
    // implements reference-type semantics currently
    var member = type.GetMember(name, BindingFlags.Public | BindingFlags.Instance).Single();

    switch (member)
    {
        case FieldInfo field:
            il.Emit(OpCodes.Ldfld, field);
            break;
        case PropertyInfo prop:
            il.EmitCall(OpCodes.Callvirt, prop.GetGetMethod(), null);
            break;
        default:
            throw new InvalidOperationException();
    }
}

If you're trying to save complexity (or are sick of InvalidProgramException), another viable approach is to use expression trees; then you have many more convenience features, but in particular things like:

var p = Expression.Parameter(typeof(Foo), "x");
var name = Expression.PropertyOrField(p, "Name");
// ...
var lambda = Expression.Lambda<YourDelegateType>(body, p);
var del = lambda.Compile();

Note that expression trees cannot be used in all scenarios; for example, they can't really be used with TypeBuilder; conversely, though, they can do things that IL-emit can't - for example, in AOT scenarios where IL-emit is prohibited, they can work as a runtime reflection evaluated tree, so it still works (but: slower). They add some additional processing (building and then parsing the tree), but: they are simpler than IL-emit, especially for debugging.




回答3:


With the clarification that by attribute you really did mean a .NET attribute (not a field or property), this becomes simpler in many ways; consider:

class SomethingAttribute : Attribute
{
    public SomethingAttribute(string name)
        => Name = name;
    public string Name { get; }
}
static class P
{
    public static void Foo([Something("Abc")] int x) {}

    static void Main()
    {
        var method = typeof(P).GetMethod(nameof(Foo));
        var p = method.GetParameters()[0];
        var attr = (SomethingAttribute)Attribute.GetCustomAttribute(
            p, typeof(SomethingAttribute));
        string name = attr?.Name;
        // you can now "ldstr {name}" etc
    }
}

The important point here is that the attribute isn't going to change at runtime - it is pure metadata; so, we can load it once with reflection when we are processing the model, then just emit the processed data, i.e. the line

// you can now "ldstr {name}" etc



回答4:


For future reference, I actually went ahead and loaded ParameterInfo using IL only. Marc's solution was good, but it quickly became infeasible after the number of parameter based attributes increased. We plan to use attributes to contain some state specific information, we would have to use a ton of reflection from outside the type to find and assign correct attribute to the field. Overall, it would have become quite hectic to deal with it.

To load ParameterInfo using IL

IL.Emit(OpCodes.Ldtoken, Method); // Method is MethodInfo as we can't use GetParameters with MethodBuilder
IL.Emit(OpCodes.Call, typeof(MethodBase).GetMethod(nameof(MethodBase.GetMethodFromHandle), new[] { typeof(RuntimeMethodHandle) }));
IL.Emit(OpCodes.Callvirt, typeof(MethodInfo).GetMethod(nameof(MethodInfo.GetParameters)));
var ILparameters = IL.DeclareLocal(typeof(ParameterInfo[]));
IL.Emit(OpCodes.Stloc, ILparameters);

that loads ParameterInfo onto stack and then stores it into a LocalBuilder called ILparameters. Note that it is an array. Items of this array can then be accessed like

IL.Emit(OpCodes.Ldloc, ILparameters);
IL.Emit(OpCodes.Ldc_I4, Number); // Replace with Ldc_I4_x if number < 8
IL.Emit(OpCodes.Ldelem_Ref);

I prefer to create two helper functions for the two code pieces. It works pretty well.



来源:https://stackoverflow.com/questions/59658409/loading-a-parameterinfo-using-il-emit

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!