Загрузка ParameterInfo с использованием IL Emit - PullRequest
2 голосов
/ 09 января 2020

В настоящее время я использую Вызов метода существующего объекта с использованием IL Emit в качестве руководства, и я уже могу делать все, что задают в вопросе. Теперь у меня есть атрибут, добавленный к параметру, и я хочу загрузить атрибут этого конкретного параметра, чтобы я мог вызвать метод внутри этого атрибута.

Я знаю, что это можно сделать, загрузив MethodInfo, а затем получить ParameterInfo и затем получить атрибут этого ParameterInfo в IL; Я просто пытаюсь не писать столько IL.

Есть ли способ загрузить атрибут параметра в IL так же, как это упомянуто в связанном посте?

Редактировать: У меня есть метод с такой подписью, как

Method([Attr] int Parameter)

, и я хочу загрузить метод, который является частью Attr. Я просто надеялся, что смогу загрузить ParameterInfo (полученный с помощью MethodInfo.GetParameters()) прямо в стек. Оказывается, LdToken на самом деле не позволяет ставить ParameterInfo. Единственный другой способ сделать это - загрузить MethodInfo (LdToken поддерживает это), а затем использовать GetParameters() в IL для получения массива параметров, а затем l oop через них в IL по одному для получите у каждого Attribute (используя .GetCustomAttribute(Type)) и затем вызовите метод для этого атрибута. Обратите внимание, что мне не нужно получать поле атрибута, мне нужно вызвать метод для этого атрибута.

Ответы [ 4 ]

1 голос
/ 09 января 2020

К, в третий раз повезло на основании другой интерпретации вопроса; здесь мы предполагаем, что хотим вызвать методы для экземпляра атрибута. Нам нужно учитывать, что только атрибуты своего рода существуют во время выполнения - мы можем создать синтетические c экземпляры атрибута, представленные метаданными, но это не особенно дешево или быстро, поэтому в идеале мы должны делайте это только один раз (в конце концов, метаданные не изменятся). Это означает, что мы можем захотеть сохранить экземпляр как поле где-нибудь. Это может быть поле экземпляра или поле stati c - во многих случаях хорошо подходит поле stati c. Рассмотрим:

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

Вывод из финала l oop (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, поэтому я пошел по этому пути:)

0 голосов
/ 10 января 2020

Для дальнейшего использования я фактически загрузил ParameterInfo, используя только IL. Решение Mar c было хорошим, но оно быстро стало невозможным после увеличения числа атрибутов на основе параметров. Мы планируем использовать атрибуты, чтобы содержать некоторую информацию, определяющую состояние c, нам пришлось бы использовать тонны отражения извне типа, чтобы найти и назначить правильный атрибут для поля. В целом, с этим справиться стало бы c.

Для загрузки ParameterInfo с использованием 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);

, который загружает ParameterInfo в стек и затем сохраняет его в LocalBuilder называется ILparameters. Обратите внимание, что это массив. К элементам этого массива можно затем обращаться, например,

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

Я предпочитаю создавать две вспомогательные функции для двух фрагментов кода. Это работает довольно хорошо.

0 голосов
/ 09 января 2020

С пояснением, что под атрибутом вы действительно имели в виду атрибут. 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
0 голосов
/ 09 января 2020

Я знаю, что это можно сделать, загрузив MethodInfo, затем получив ParameterInfo, а затем получив атрибут этого ParameterInfo в IL; Я просто стараюсь не писать столько IL.

Да, в значительной степени, в IL; IL мощен, но не особенно лаконичен или прост. Как и в связанном посте, вы в конечном итоге загрузите параметр (ldarg или ldarga, может быть, некоторые .s), а затем в зависимости от того, является ли элемент полем или свойством, с помощью ldfld или callvirt в свойстве getter. Около 3 строк, поэтому не огромный ; возможно что-то вроде:

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 не может - например, в сценарии AOT ios, где IL-emit запрещен, они могут работать как дерево оценки отражения во время выполнения, поэтому все еще работает (но: медленнее). Они добавляют некоторую дополнительную обработку (сборку и последующий анализ дерева), но: они на проще , чем IL-emit, особенно для отладки.

...