c# Выдача делегата динамического метода для загрузки параметризованного конструктора Проблема

Я пытаюсь создать представление делегата конструктора, выпустив динамический метод, который должен соответствовать этой очень "свободно типизированной" сигнатуре, чтобы его можно было использовать с любым типом параметризованного конструктора:

public delegate Object ParamsConstructorDelegate(params object[] parameters);

и код для этого создания делегата выглядит так (обратите внимание, что это для Silverlight)

public static ParamsConstructorDelegate CreateDelegate(ConstructorInfo constructor)
    {
        Guard.ArgumentNotNull(constructor, "constructor");
        Guard.ArgumentValue(constructor.GetParameters().Length == 0, MUSTBE_PARAMETERIZED_CONSTRUCTOR);

        var _argumentTypes = new Type[] { typeof(object[]) };
        var _parameters = constructor.GetParameters();
        var _parameterTypes = _parameters.Select((p) => p.ParameterType).ToArray();

        var _sourceType = constructor.DeclaringType;
        var _method = new DynamicMethod(constructor.Name, _sourceType, _argumentTypes);
        var _gen = _method.GetILGenerator();

        for (var _i = 0; _i < _parameters.Length; _i++)
        {
            if (_parameters[_i].IsOut || _parameterTypes[_i].IsByRef)
            {
                if (_i < 128)
                {
                    _gen.Emit(OpCodes.Ldarga_S, (byte)_i);
                }
                else
                    _gen.Emit(OpCodes.Ldarga, _i);
            }
            else
            {
                switch (_i)
                {
                    case 0:
                        _gen.Emit(OpCodes.Ldarg_0, _i);
                        break;
                    case 1:
                        _gen.Emit(OpCodes.Ldarg_1, _i);
                        break;
                    case 2:
                        _gen.Emit(OpCodes.Ldarg_2, _i);
                        break;
                    case 3:
                        _gen.Emit(OpCodes.Ldarg_3, _i);
                        break;
                    default:
                        if (_i < 128)
                            _gen.Emit(OpCodes.Ldarg_S, (byte)_i);
                        else
                            _gen.Emit(OpCodes.Ldarg, _i);
                        break;
                }
            }
        }
        _gen.Emit(OpCodes.Newobj, constructor);
        _gen.Emit(OpCodes.Ret); ;

        return (ParamsConstructorDelegate)_method.CreateDelegate(typeof(ParamsConstructorDelegate));
    }

Теперь я получаю сообщение «Операция может дестабилизировать среду выполнения». исключение проверки, очевидно, что IL неверен, поэтому я надеюсь, что кто-то может меня исправить.

Спасибо


person Orktane    schedule 28.02.2010    source источник
comment
Префикс подчеркивания для местных жителей — это стиль, которого я не очень много видел.   -  person Phil Cooper    schedule 03.12.2012


Ответы (2)


Я вижу две проблемы; во-первых, вам не нужен _i для случаев с Ldarg_0 по Ldarg_3 (это неявно). Во-вторых, ваш делегат имеет только один аргумент (массив). Вам нужно будет получить элементы из массива и преобразовать - что-то вроде приведенного ниже (который обрабатывает только передачу по значению; для ref/out вам нужно будет определить локальный и использовать stloc/ldloca/и т. д.) :

using System;
using System.Reflection.Emit;
public delegate object ParamsConstructorDelegate(params object[] parameters);
public class Foo
{
    string s;
    int i;
    float? f;
    public Foo(string s, int i, float? f)
    {
        this.s = s;
        this.i = i;
        this.f = f;
    }
}

static class Program
{
    static void Main()
    {
        var ctor = Build(typeof(Foo));
        Foo foo1 = (Foo)ctor("abc", 123, null);
        Foo foo2 = (Foo)ctor(null, 123, 123.45F);
    }
    static ParamsConstructorDelegate Build(Type type)
    {
        var mthd = new DynamicMethod(".ctor", type,
            new Type[] { typeof(object[]) });
        var il = mthd.GetILGenerator();
        var ctor = type.GetConstructors()[0]; // not very robust, but meh...
        var ctorParams = ctor.GetParameters();
        for (int i = 0; i < ctorParams.Length; i++)
        {
            il.Emit(OpCodes.Ldarg_0);
            switch (i)
            {
                case 0: il.Emit(OpCodes.Ldc_I4_0); break;
                case 1: il.Emit(OpCodes.Ldc_I4_1); break;
                case 2: il.Emit(OpCodes.Ldc_I4_2); break;
                case 3: il.Emit(OpCodes.Ldc_I4_3); break;
                case 4: il.Emit(OpCodes.Ldc_I4_4); break;
                case 5: il.Emit(OpCodes.Ldc_I4_5); break;
                case 6: il.Emit(OpCodes.Ldc_I4_6); break;
                case 7: il.Emit(OpCodes.Ldc_I4_7); break;
                case 8: il.Emit(OpCodes.Ldc_I4_8); break;
                default: il.Emit(OpCodes.Ldc_I4, i); break;
            }
            il.Emit(OpCodes.Ldelem_Ref);
            Type paramType = ctorParams[i].ParameterType;
            il.Emit(paramType.IsValueType ? OpCodes.Unbox_Any
                : OpCodes.Castclass, paramType);
        }
        il.Emit(OpCodes.Newobj, ctor);
        il.Emit(OpCodes.Ret);
        return (ParamsConstructorDelegate)
            mthd.CreateDelegate(typeof(ParamsConstructorDelegate));
    }
}

Для информации - я ленивый - если я хочу знать, какой IL писать, я пишу его на C #, а затем загружаю в рефлектор. Например, для этого я написал метод:

static object CreateFoo(object[] vals)
{
    return new Foo((string)vals[0], (int)vals[1], (float?)vals[2]);
}

и перевернул его оттуда

person Marc Gravell    schedule 28.02.2010
comment
Спасибо за быстрый ответ, Марк, не могли бы вы указать мне, как расширить массив и вставить значения в конструктор, потому что я не уверен, как вывести значения массива, а затем использовать IL. Еще раз спасибо. - person Orktane; 01.03.2010
comment
Я только что видел это, и это работает просто отлично. Кроме того, спасибо, что показали мне статический конструктор, который вы перевернули, это именно та часть, которую мне не хватало, и теперь все подходит. Ценю вашу помощь, ура. - person Orktane; 01.03.2010

Мне очень трудно понять, что означает сообщение об ошибке "Операция может дестабилизировать среду выполнения" при использовании Reflection.Emit — CLR не дает здесь много полезной информации. Один трюк, который вы можете использовать, чтобы получить больше информации о проблеме, состоит в том, чтобы изменить свой код, чтобы он выдавал код какой-либо временной сборке (в дополнение к выдаче динамического делегата) при работе в режиме отладки.

Затем вы можете использовать инструмент peverify (из командной строки Visual Studio), который обычно дает вам больше информации о проблемах с сгенерированным кодом IL:

 > peverify assembly.dll

Вам нужно будет использовать такие классы, как AssemblyBuilder и ModuleBuilder, для создания сборки. Затем вы можете запустить основную часть (которая использует ILGenerator) два раза, чтобы сгенерировать динамический делегат (для фактического запуска) и временную сборку (для отладки). Я считаю, что peverify дает вам гораздо лучшую информацию.

person Tomas Petricek    schedule 01.03.2010
comment
Томас, это очень полезный совет по IL и делегатам, потому что на самом деле эти исключения так же полезны, как сказать, что вы где-то накосячили, теперь идите исправляйте :) Ну, пока Марк выше меня выручил, но большое спасибо за совет буду иметь в виду.. - person Orktane; 01.03.2010