使用 IL Emit 加载 ParameterInfo

Loading a ParameterInfo using IL Emit

我目前正在使用 作为指导,我已经可以做任何有问题的事情了。现在,我向参数添加了一个属性,我想加载该特定参数的属性,以便我可以调用该属性内的方法。

我知道这可以通过加载 MethodInfo 然后获取 ParameterInfo 然后在 IL 中获取 ParameterInfo 的属性来完成;我只是想避免写那么多 IL。

有没有办法像链接 post 中提到的那样在 IL 中加载参数的属性?

编辑: 我有一个签名像

的方法
Method([Attr] int Parameter)

我想加载属于 Attr 的方法。我只是希望我可以将 ParameterInfo(使用 MethodInfo.GetParameters() 获得)直接加载到堆栈上。事实证明,LdToken 确实不允许放置 ParameterInfo。我能想到的唯一其他方法是加载 MethodInfoLdToken 支持),然后在 IL 中使用 GetParameters() 获取一组参数,然后在 IL 中循环遍历它们通过一个来获取每个人的 Attribute(使用 .GetCustomAttribute(Type)),然后调用该属性的方法。请注意,我不必获取属性的字段,我需要对该属性调用方法。

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.

是的,在伊利诺伊州差不多就是这样; IL 功能强大,但不是特别简洁或简单。就像在链接 post 中一样,您最终会加载参数(ldarg 或 ldarga,也许是一些 .s),然后根据成员是字段还是 属性,使用 ldfld或在 属性 getter 上调用 virt。大约 3 行,所以不是 huge;也许是这样的:

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();
    }
}

如果您试图节省复杂性(或者厌倦了 InvalidProgramException),另一种可行的方法是使用表达式树;然后你有更多的便利功能,但特别是像:

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

注意,表达式树并不是所有场景都用得上的;例如,它们不能真正与 TypeBuilder 一起使用;但是,相反地,它们 可以 做 IL-emit 不能做的事情——例如,在禁止 IL-emit 的 AOT 场景中,它们可以作为运行时反射评估树工作,所以它仍然 有效 (但是:速度较慢)。它们添加了一些额外的处理(构建然后解析树),但是:它们比 IL-emit 更简单,特别是对于调试。

通过澄清您所说的属性确实是指 .NET 属性(不是字段或 属性),这在很多方面变得更简单;考虑:

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
    }
}

这里的重点是该属性不会在运行时更改 - 它是纯元数据;所以,我们可以在处理模型时用反射加载一次,然后只发出处理过的数据,即行

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

K,第三次幸运基于另一个对问题的解释;在这里,我们假设我们想要调用属性实例上的方法。我们需要考虑到只有 kinda sorta 属性在运行时存在——我们可以创建由元数据表示的属性的合成实例,但这并不是特别便宜或快速,所以我们应该理想情况下只执行一次(毕竟元数据不会改变)。这意味着我们可能希望将实例作为字段存储在某处。这可以是实例字段,也可以是静态字段——在许多情况下,静态字段就可以了。考虑:

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);
    }
}

最终循环(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

作为旁注;在许多方面,使用表达式树更容易,因为表达式树有Expression.Constant可以属性实例,并且在内部被视为一个字段。但是你提到了TypeBuilder,所以我就这样走了:)

为了将来参考,我实际上只使用 IL 加载了 ParameterInfo。 Marc 的解决方案很好,但随着基于参数的属性数量增加,它很快变得不可行。我们计划使用属性来包含一些特定于状态的信息,我们将不得不使用来自类型外部的大量反射来找到正确的属性并将其分配给字段。总的来说,处理它会变得非常忙碌。

使用 IL

加载 ParameterInfo
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);

ParameterInfo 加载到堆栈上,然后将其存储到名为 ILparametersLocalBuilder 中。请注意,它是一个数组。然后可以像

一样访问这个数组的项目
IL.Emit(OpCodes.Ldloc, ILparameters);
IL.Emit(OpCodes.Ldc_I4, Number); // Replace with Ldc_I4_x if number < 8
IL.Emit(OpCodes.Ldelem_Ref);

我更喜欢为这两个代码片段创建两个辅助函数。效果很好。