2015-02-20 2 views
0

Когда я пытаюсь вызвать выражение лямбда, я получаю исключение. По-видимому, это связано с тем, что лямбда генерирует частный статический метод. Как я могу достичь того, чего хочу?Вызвать вызов лямбда-выражения

static void Main(string[] args) 
    { 
     var typeBuilder = CreateTypeBuilder(); 
     AddStaticMethodAsAProperty(typeBuilder, "StaticPublic", Return1); 
     AddStaticMethodAsAProperty(typeBuilder, "StaticPrivate", Return2); 
     AddStaticMethodAsAProperty(typeBuilder, "Lambda",() => 3); 

     var newType = typeBuilder.CreateType(); 
     dynamic newObject = Activator.CreateInstance(newType); 
     var resultFromStatic = newObject.StaticPublic; //Ok 
     var resultFromStaticPrivate = newObject.StaticPrivate; //Additional information: Attempt by method 'NewType.get_StaticPrivate()' to access method 'Test.Program.Return2()' failed. 
     var resultFromLambda = newObject.Lambda; //failed with Additional information: Attempt by method 'NewType.get_Lambda()' to access method 'Test.Program.<Main>b__3()' failed. 
    } 

    public static void AddStaticMethodAsAProperty(TypeBuilder typeBuilder, string propertyName, Func<int> methodToAdd) 
    { 
     var propertyType = methodToAdd.Method.ReturnType; 

     var getPropertyMethodBuilder = typeBuilder.DefineMethod(string.Format("get_{0}", propertyName), MethodAttributes.Public | MethodAttributes.HideBySig, propertyType, Type.EmptyTypes); 

     var getIL = getPropertyMethodBuilder.GetILGenerator(); 

     getIL.Emit(OpCodes.Call, methodToAdd.Method); 
     getIL.Emit(OpCodes.Ret); 

     var propertyBuilder = typeBuilder.DefineProperty(propertyName, PropertyAttributes.None, propertyType, new Type[] { }); 
     propertyBuilder.SetGetMethod(getPropertyMethodBuilder); 
    } 

    public static int Return1() 
    { 
     return 1; 
    } 

    private static int Return2() 
    { 
     return 1; 
    } 

    private static TypeBuilder CreateTypeBuilder() 
    { 
     var newTypeName = "NewType"; 
     var newAssemblyName = new AssemblyName(newTypeName); 
     var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(newAssemblyName, AssemblyBuilderAccess.Run); 
     var moduleBuilder = assemblyBuilder.DefineDynamicModule("MainModule"); 
     var typeBuilder = moduleBuilder.DefineType(newTypeName 
              , TypeAttributes.Public | 
              TypeAttributes.Class | 
              TypeAttributes.AutoClass | 
              TypeAttributes.AnsiClass | 
              TypeAttributes.BeforeFieldInit | 
              TypeAttributes.AutoLayout 
              , null); 
     return typeBuilder; 
    } 
+1

Что именно вы намерены положить в лямбда? Сгенерированный класс не может получить доступ к личным данным в вашей исходной сборке, поэтому только тривиальные лямбды будут работать. Вам нужно создать отдельную сборку и класс здесь или у вас есть сценарий, который может помочь вам построить деревья выражений? –

+0

Да, что вы пытаетесь сделать здесь? Построить класс прокси во время выполнения или что-то еще? – Luaan

+0

@ Luaan: Да, этого я и пытаюсь достичь. Во время выполнения я хочу создать тип, чей геттер «маршрутизирован», как я определил его во время компиляции. – Toto

ответ

2

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

static void Main(string[] args) 
    { 
     var typeBuilder = CreateTypeBuilder(); 
     AddStaticMethodAsAProperty(typeBuilder, "StaticPublic", Return1); 
     AddStaticMethodAsAProperty(typeBuilder, "StaticPrivate", Return2); 
     AddStaticMethodAsAProperty(typeBuilder, "Lambda",() => 3); 

     var newType = typeBuilder.CreateType(); 

     dynamic newObject = Activator.CreateInstance(newType); 
     foreach (var onNewObjectCreated in _onNewObjectCreated) 
     { 
      onNewObjectCreated(newObject); 
     } 

     var resultFromStatic = newObject.StaticPublic; //Ok 1 
     var resultFromStaticPrivate = newObject.StaticPrivate; //Ok 2 
     var resultFromLambda = newObject.Lambda; //Ok 3 
    } 

    private static List<Action<object>> _onNewObjectCreated = new List<Action<object>>(); 
    public static void AddStaticMethodAsAProperty<T>(TypeBuilder typeBuilder, string propertyName, Func<T> valueGetter) 
    { 
     var propertyType = valueGetter.Method.ReturnType; 
     var delegateFieldBuilder = typeBuilder.DefineField(string.Format("_backingDelegate{0}", propertyName), valueGetter.GetType(), FieldAttributes.Private); 

     var getMethod = typeBuilder.DefineMethod(string.Format("get_{0}", propertyName), MethodAttributes.Public | MethodAttributes.HideBySig, propertyType, Type.EmptyTypes); 


     Action<object> setDelegateFieldAction = newlyCreatedObject => { newlyCreatedObject.GetType().GetField(delegateFieldBuilder.Name, BindingFlags.NonPublic | BindingFlags.Instance).SetValue(newlyCreatedObject, valueGetter); }; 
     _onNewObjectCreated.Add(setDelegateFieldAction); 

     var il = getMethod.GetILGenerator(); 
     il.Emit(OpCodes.Ldarg_0);//stack [this] 
     il.Emit(OpCodes.Ldfld, delegateFieldBuilder);//stack [this._backingDelegateXXX] 
     il.Emit(OpCodes.Callvirt, valueGetter.GetType().GetMethod("Invoke"));//stack [valueReturnedByTheDelegate] 
     il.Emit(OpCodes.Ret); 
     var propertyBuilder = typeBuilder.DefineProperty(propertyName, PropertyAttributes.None, propertyType, new Type[] { }); 
     propertyBuilder.SetGetMethod(getMethod); 
    } 

    public static int Return1() 
    { 
     return 1; 
    } 

    private static int Return2() 
    { 
     return 2; 
    } 
2

methodToAdd является делегатом. Вы пытаетесь вызвать Method, который он содержит внутри, но это неправильный подход. Вместо этого вы хотите выполнить делегат, и это немного сложнее.

В принципе, вам необходимо позвонить по методу Invoke на methodToAdd. Это также означает, что вам нужно поставить делегат где-нибудь из окончательного кода. Другими словами, вам нужно выполнить большую часть работы, которую обычно выполняет компилятор C#, - создавать анонимные классы, содержащие ссылку на делегат. Вызов callvirt Func<int>.Invoke на этой ссылке является легкой частью.

К счастью, новый материал Expression является абсолютно потрясающим. Таким образом, вы должны быть в состоянии сделать это:

Expression<Func<T>> expr =() => methodToAdd(); 
expr.CompileToMethod(getPropertyMethodBuilder); 

Всегда старается держаться подальше, насколько это возможно, от излучающего IL - даже ilasm сами делает много работы, что ILGenerator не делает. Но он все равно должен выдавать действительные ИЛ - и call по частному методу в неправильном объеме, конечно не действительный IL.