How do I create mutually callable MethodInfos from MethodBuilders created from LambdaExpressions? - c#

I'm currently working on an compiler in C#, where the behaviour is defined by LambdaExpressions, and then using CompileToMethod, transformed into MethodBuilders and saved to DLL. All functions are public and static.
However, I could not find a way to extract usable MethodInfo (or another method of reference) from the MethodBuilder until the behaviour is defined and declaring type is created/sealed. That means that at that until that point, it is impossible to use Expression.Call to call these functions. That makes self-recursion or mutual referencing between two functions impossible.
I ended up using Reflection to invoke the functions at runtime, but it's very suboptimal, and I'm still curious if there's a better way.
How do i ensure functions created with LambdaExpression.CompileToMethod(MethodBuilder) can self-call?
Alternatively, is there any other way to use LambdaExpressions which would allow this and support saving as a static method to a dll?

I hope this helps.
This is complete code example which produces runtime defined type with single static recursive method.
For the simplicity of the example the recursive method is infinite - at the end of the Main method the recursive method is called
static void Main(string[] args)
{
var moduleBuilder = CreateDynamicModuleBuilder();
var typeBuilder = moduleBuilder.DefineType("Person", TypeAttributes.Public | TypeAttributes.Class);
var methodBuilder = typeBuilder.DefineMethod("SayHello", MethodAttributes.Static | MethodAttributes.Public);
var methodExpression = CreateRecursiveExpression();
var lambda = Expression.Lambda(methodExpression);
lambda.CompileToMethod(methodBuilder);
var typeInfo = typeBuilder.CreateType();
var methodInfo = typeInfo.GetMethod("SayHello", BindingFlags.Public | BindingFlags.Static);
methodInfo.Invoke(null, null);
}
private static Expression CreateRecursiveExpression()
{
var methodInfo = typeof(Console).GetMethod("WriteLine", new[] { typeof(String) });
var arg = Expression.Constant("Hello");
var consoleCall = Expression.Call(methodInfo, arg);
var sayHelloActionVariable = Expression.Variable(typeof(Action), "sayHelloAction");
var block = Expression.Block(
new[] { sayHelloActionVariable },
Expression.Assign(
sayHelloActionVariable,
Expression.Lambda(
Expression.Block(
consoleCall,
Expression.Invoke(sayHelloActionVariable)
)
)
),
Expression.Invoke(sayHelloActionVariable)
);
return block;
}
private static ModuleBuilder CreateDynamicModuleBuilder()
{
var name = new AssemblyName("Example.DynamicRecursion");
var am = AssemblyBuilder.DefineDynamicAssembly(name, AssemblyBuilderAccess.RunAndSave);
var mb = am.DefineDynamicModule(name.Name, $"{name.Name}.dll");
return mb;
}
This code will create type with the following signature
public class Person
{
public static void SayHello()
{
Action sayHelloAction;
sayHelloAction = () =>
{
Console.WriteLine("Hello");
sayHelloAction();
}
sayHelloAction();
}
}

Related

When implementing an interface that has a method with 'in' parameter by TypeBuilder.CreateType, TypeLoadException is thrown

Before beginning, this is my first question on SO. So there might be faults or lack of information about the problem. Please let me know if there's something that I need to correct. Thanks.
Using TypeBuilder, I'm building a class that implements an interface that contains a method. After implementing that method with ILGenerator, then I call TypeBuilder.CreateType() and everything goes well in the normal case.
But if the method contains any parameter with the in modifier, also known as readonly reference for value types, TypeBuilder.CreateType() throws TypeLoadException("Method 'SomeMethod' ... does not have an implementation.").
Unlike the usual case of TypeLoadException that implemented method with the same signature as the one declared in the interface(s) doesn't exist, this problem is raised only when the method contains in parameter(s) even signatures are the same. When I remove or change the in modifier to ref or out, TypeBuilder.CreateType() successfully recognizes the generated method as an implementation of one declared in the interface, and the type is built normally.
Here's a fully compilable example:
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Threading;
namespace EmitMethodWithInParamTest
{
public struct StructParam
{
public String Data;
}
public interface ISomeInterface
{
Int32 SomeMethod(in StructParam param);
}
static class EmitExtension
{
public static void ReplicateCustomAttributes(this ParameterBuilder paramBuilder, ParameterInfo paramInfo)
{
foreach (var attrData in paramInfo.GetCustomAttributesData())
{
var ctorArgs = attrData.ConstructorArguments.Select(arg => arg.Value).ToArray();
// Handling variable arguments
var ctorParamInfos = attrData.Constructor.GetParameters();
if (ctorParamInfos.Length > 0 &&
ctorParamInfos.Last().IsDefined(typeof(ParamArrayAttribute)) &&
ctorArgs.Last() is IReadOnlyCollection<CustomAttributeTypedArgument> variableArgs)
{
ctorArgs[ctorArgs.Length - 1] = variableArgs.Select(arg => arg.Value).ToArray();
}
var namedPropArgs = attrData.NamedArguments.Where(arg => !arg.IsField);
var namedPropInfos = namedPropArgs.Select(arg => (PropertyInfo)arg.MemberInfo).ToArray();
var namedPropValues = namedPropArgs.Select(arg => arg.TypedValue.Value).ToArray();
var namedFieldArgs = attrData.NamedArguments.Where(arg => arg.IsField);
var namedFieldInfos = namedFieldArgs.Select(arg => (FieldInfo)arg.MemberInfo).ToArray();
var namedFieldValues = namedFieldArgs.Select(arg => arg.TypedValue.Value).ToArray();
var attrBuilder = new CustomAttributeBuilder(attrData.Constructor,
ctorArgs, namedPropInfos, namedPropValues, namedFieldInfos, namedFieldValues);
paramBuilder.SetCustomAttribute(attrBuilder);
}
}
}
class Program
{
static Program()
{
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("en-us");
}
static void Main(String[] args)
{
var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("DynamicAssembly"), AssemblyBuilderAccess.Run);
var moduleBuilder = assemblyBuilder.DefineDynamicModule("MainModule");
var typeBuilder = moduleBuilder.DefineType("SomeClass",
TypeAttributes.Public | TypeAttributes.Class | TypeAttributes.AutoClass | TypeAttributes.AnsiClass | TypeAttributes.BeforeFieldInit | TypeAttributes.AutoLayout,
null /*base class*/,
new[] { typeof(ISomeInterface) });
var methodInfoToImpl = typeof(ISomeInterface).GetMethod(nameof(ISomeInterface.SomeMethod));
var paramInfos = methodInfoToImpl.GetParameters();
var methodBuilder = typeBuilder.DefineMethod(methodInfoToImpl.Name,
MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.Final,
CallingConventions.HasThis,
methodInfoToImpl.ReturnType,
paramInfos.Select(pi => pi.ParameterType).ToArray());
foreach (var paramInfo in paramInfos)
{
// paramInfo.Position is zero-based but DefineParameter requires 1-based index.
var paramBuilder = methodBuilder.DefineParameter(paramInfo.Position + 1, paramInfo.Attributes, paramInfo.Name);
if (paramInfo.Attributes.HasFlag(ParameterAttributes.HasDefault))
{
paramBuilder.SetConstant(paramInfo.DefaultValue);
}
paramBuilder.ReplicateCustomAttributes(paramInfo);
}
// Dummy implementation for example. Always throws NotImplementedException.
var ilGen = methodBuilder.GetILGenerator();
ilGen.Emit(OpCodes.Newobj, typeof(NotImplementedException).GetConstructor(Type.EmptyTypes));
ilGen.Emit(OpCodes.Throw);
var builtType = typeBuilder.CreateType(); // <- TypeLoadException("Method 'SomeMethod' in type 'SomeClass' from assembly 'DynamicAssembly, ...' does not have an implementation.") is thrown.
var generatedObj = (ISomeInterface)Activator.CreateInstance(builtType);
var someParam = new StructParam() { Data = "SomeData" };
var result = generatedObj.SomeMethod(in someParam); // <- NotImplementedException expected by dummy implementation if executed.
Console.WriteLine($"Result: {result}");
}
}
}
This code is also uploaded to Pastebin.
While digging down this problem, I found that the in parameter has two custom attributes, InteropServices.InAttribute and CompilerServices.IsReadOnlyAttribute. But when I generate a method without implementing the interface (this succeeds normally because no signature matching required), in parameter of generated method has only one custom attribute, InAttribute. So I replicated all custom attributes of parameters from the interface, but still TypeLoadException is being raised.
I've tested this on .NET Framework 4.6.1 and .NET Core 2.2 with C# 7.2 and 7.3. And all environments gave me the same exception. I'm using Visual Studio 2017 on Windows.
Is there anything that I have missed or are there any workarounds?
Thank you for any help in advance.
After writing the question above, I've been investigated built binary of sample code in IL and source code of CoreCLR for a few days, and now I found the problem and solution.
In short, required and optional custom modifiers of return type and each parameter type take a part of method signature like each types do, and it had to be replicated manually. I thought that it will be done by passing ParameterAttributes.In to MethodBuilder.DefineParameter and replicating the custom attribute InAttribute, but it was wrong.
And, among in, ref and out modifiers, only in emits a required custom modifier to specified parameter. In contrast, ref and out are represented only with their type itself. This is the reason why only in didn't work as expected.
To replicate custom modifiers, call to TypeBuilder.DefineMethod need be modified like this:
var methodBuilder = typeBuilder.DefineMethod(methodInfoToImpl.Name,
MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.Final,
CallingConventions.HasThis,
methodInfoToImpl.ReturnType,
methodInfoToImpl.ReturnParameter.GetRequiredCustomModifiers(), // *
methodInfoToImpl.ReturnParameter.GetOptionalCustomModifiers(), // *
paramInfos.Select(pi => pi.ParameterType).ToArray(),
paramInfos.Select(pi => pi.GetRequiredCustomModifiers()).ToArray(), // *
paramInfos.Select(pi => pi.GetOptionalCustomModifiers()).ToArray() // *
);
Marked lines with // * are newly added to replicate custom modifiers of return/parameter types.
Or, we can do this by calling MethodBuilder.SetSignature method after calling DefineMethod without any type and custom modifiers arguments. If we decided to call SetSignature separately, we need to call it before any DefineParameter, SetCustomAttribute, Equals(Object), SetImplementationFlags, getter of property Signature and many other methods that call the internal method MethodBuilder.GetMethodSignature() that cache bytes representing method signature.
Thank you for reading and giving me advice. :)

ILGenerator call instance method with parameter

I am trying to construct an instance of a generic type and call a method on that instance. Then return the result of the method.
MethodInfo methodInfo = ...;
...
var genericType = typeof(GenericType<>).MakeGenericType(typeof(TOutput));
il.Emit(OpCodes.Newobj, genericType.GetConstructor(Type.EmptyTypes));
il.Emit(OpCodes.Ldobj, methodInfo);
il.Emit(OpCodes.Callvirt, genericeClientHelper.GetMethod("MethodName", new Type[] { typeof(MethodInfo) }));
il.Emit(OpCodes.Ret);
I keep getting a 'System.BadImageFormatException: 'Bad class token.'' exception.
GenericType class looks like this
public class GenericType<T>
{
public T MethodName(MethodInfo methodInfo)
{
...
}
}
You're confusing the boundary between the generated program, and the generating program.
Specifically, your generating program, when run, constructs an instance of an object (a MethodInfo instance) and then attempts to generate a program that uses that instance - which it can't because that instance doesn't exist in the generated program, it exists in the memory of the generating program.
You have to construct the instance of MethodInfo inside your generated program - you have to write the Emit code to generate the IL that constructs the MethodInfo instance.
What you're trying to do makes about as much sense as doing the following:
Person person = new Person( "Antiduh", "United States" );
var genericType = typeof(GenericType<>).MakeGenericType(typeof(TOutput));
il.Emit(OpCodes.Newobj, genericType.GetConstructor(Type.EmptyTypes));
// This doesn't make sense. The object referred to by
// my `person` variable doesn't exist in the generated program.
il.Emit(OpCodes.Ldobj, person);
il.Emit(OpCodes.Callvirt, genericeClientHelper.GetMethod("MethodName", new Type[] { typeof(MethodInfo) }));
il.Emit(OpCodes.Ret);
Thats problem number 1.
Problem number 2 is that you're using the wrong opcode when trying to provide an argument to a method - Ldobj doesn't do what you think it does.
Instead of using Ldobj, you'll have to load the reference by whatever means you fix your generation code to create the internal methodInfo. It'll probably be local, so you'll probably end up using Ldloc or some form thereof.
To come full circle, the reason why you're getting the error "Bad class token" is that the value that is supposed to follow Ldobj in the compiled IL is supposed to be a class metadata token. What you provided was not a class token, hence the error.
As a demonstration, below is a complete program that emulates what you're attempting to do.
private static void BuildAssembly()
{
AssemblyName assemblyName;
AssemblyBuilder assmBuilder;
ModuleBuilder modBuilder;
assemblyName = new AssemblyName( "Generated" );
// Note the save directory is the directory containing this project's solution file.
assmBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(
assemblyName,
AssemblyBuilderAccess.RunAndSave,
Assembly.GetExecutingAssembly().Location + #"\..\..\..\.."
);
modBuilder = assmBuilder.DefineDynamicModule(
assemblyName.Name,
assemblyName.Name + ".dll",
true
);
/*
* public class GenericsDemo {
* }
*/
TypeBuilder typeBuilder = modBuilder.DefineType(
"Generated.GenericsDemo",
TypeAttributes.Public
);
BuildCallListMethod( typeBuilder );
typeBuilder.CreateType();
assmBuilder.Save( assemblyName.Name + ".dll" );
}
private static void BuildCallListMethod( TypeBuilder typeBuilder )
{
// public void CallList() {
// List<object> list = new List<object>();
// object thing = new object();
// list.Add(thing);
// }
var listOfObject = typeof( List<object> );
var objType = typeof( object );
// public void CallList() {
var method = typeBuilder.DefineMethod(
"CallList",
MethodAttributes.Public | MethodAttributes.HideBySig,
CallingConventions.HasThis
);
var gen = method.GetILGenerator();
// List<int> list;
var listLocal = gen.DeclareLocal( listOfObject );
listLocal.SetLocalSymInfo( "list" );
// object thing;
var thingLocal = gen.DeclareLocal( objType );
thingLocal.SetLocalSymInfo( "thing" );
// list = new List<object>();
gen.Emit( OpCodes.Newobj, listOfObject.GetConstructor( Type.EmptyTypes ) );
gen.Emit( OpCodes.Stloc_0 );
// thing = new object();
gen.Emit( OpCodes.Newobj, objType.GetConstructor( Type.EmptyTypes ) );
gen.Emit( OpCodes.Stloc_1 );
// list.Add( thing );
gen.Emit( OpCodes.Ldloc_0 ); // loads `list`.
gen.Emit( OpCodes.Ldloc_1 ); // loads `thing`.
gen.EmitCall( OpCodes.Callvirt, listOfObject.GetMethod( "Add" ), null );
gen.Emit( OpCodes.Ret );
}

Convert Reflection.Emit to Roslyn

I need to convert an existing code that uses Reflection.Emit to Roslyn.
The code I have currently is basically this:
var assemblyName = new AssemblyName("AssemblyName");
var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Save);
var builder = assemblyBuilder.DefineDynamicModule("test", "test.dll");
var type = builder.DefineType("Entry", TypeAttributes.Public, typeof(object), null);
var method = type.DefineMethod("###Start_v1.4.3.0", MethodAttributes.Public | MethodAttributes.HideBySig);
method.SetReturnType(typeof(void));
var generator = method.GetILGenerator();
generator.Emit(OpCodes.Nop);
generator.Emit(OpCodes.Ret);
type.CreateType();
assemblyBuilder.Save(#"test.dll");
As you can see, there is a class named Entry with a method called ###Start_v1.4.3.0.
We're using this for more than 7 years now but evereytime we need to change anything, it's a pain because we need to use those Emits and it's not trivial.
It would be great if we could just have Roslyn to compile the code:
public class Entry
{
public void ###Start_v1.4.3.0()
{
}
}
But it doesn't work due to the method name being invalid.
The compiled dll is used by a third party component and it looks for this class and method name to execute. We tried to reach the developers to have a new version but no luck.
I think Roslyn won't compile this at all, but I believe there might be a way to rename the method name later from let's say just Start() to ###Start_v1.4.3.0()... I just don't know how to do this.
Any help will be very welcome.
If the only problem is the illegal method name, you can easily resolve that issue.
Compile the dll with a legal name, and then you have several ways to change the method name.
With mono.cecil its pretty simple.
public void ChangeMethodName()
{
//Before changing the method name
var assem = Assembly.LoadFile(#"C:\temp\ClassLibrary1.dll");
Console.WriteLine(
assem.GetType("ClassLibrary1.Class1").
GetMethod("Start", BindingFlags.Static | BindingFlags.Public).
Invoke(null, null));
// Change the name
var module = ModuleDefinition.ReadModule(#"C:\temp\ClassLibrary1.dll");
TypeDefinition myType =
module.Types.First(type => type.Name == "Class1");
var method = myType.Methods.First(m => m.Name == "Start");
method.Name = "###Start_v1.4.3.0";
module.Write(#"C:\temp\ClassLibrary1_new.dll");
//After changing the method name
assem = Assembly.LoadFile(#"C:\temp\ClassLibrary1_new.dll");
Console.WriteLine(
assem.GetType("ClassLibrary1.Class1").
GetMethod("###Start_v1.4.3.0",
BindingFlags.Static|BindingFlags.Public).
Invoke(null, null));
}
public class Class1
{
public static string Start()
{
return $"my name is {MethodBase.GetCurrentMethod().Name}";
}
}

How do I work around the error "ByRef return value not supported in reflection invocation" in C#?

I have a .Net library supplied by a third party. I did reflection on one of their classes and found a member method. The signature was...
Byte& FooBar()
So, I wanted to call this method through reflection and got the exception "ByRef return value not supported in reflection invocation."
Here is what I've tried...
var strm = new TheirClass();
var t = strm.GetType();
var ms = t.GetMembers(
BindingFlags.Static|BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
foreach (var m in ms)
{
Debug.WriteLine(String.Format("Name: {0}: {1}", m.Name, m.ToString()));
// ...
// Name: FooBar: Byte& FooBar()
// ...
}
var meth = t.GetMethod("FooBar");
object returnValue = meth.Invoke(strm, new object[] { }); //throw exception
I have tried supplying parameters as in calling functions with ref parameters, but that made no difference.
I would like to work around this exception in C#.
Per the comments: here is how it can be done from CIL, which can be generated from C#.
I was hoping to use a DynamicMethod, but I cannot manage to get this working without creating a custom delegate type at runtime, so I needed to use AssemblyBuilder instead.
using System;
using System.Reflection;
using System.Reflection.Emit;
public delegate void CallBadFunction(Delegate d, Callback c);
public delegate void Callback(ref int i);
static class Program
{
static int i;
static object BadMethod()
{
return i;
}
static MethodInfo GetBadMethod()
{
return typeof(Program).GetMethod("BadMethod", BindingFlags.Static | BindingFlags.NonPublic);
}
static void Main()
{
var badMethod = GetBadMethod();
var assembly = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("-"), AssemblyBuilderAccess.Run);
var module = assembly.DefineDynamicModule("-");
var badDelegate = module.DefineType("BadDelegateType", TypeAttributes.Public | TypeAttributes.Class | TypeAttributes.Sealed, typeof(MulticastDelegate));
var badDelegateCtor = badDelegate.DefineConstructor(MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, CallingConventions.Standard, new Type[] { typeof(object), typeof(IntPtr) });
badDelegateCtor.SetImplementationFlags(MethodImplAttributes.Runtime | MethodImplAttributes.Managed);
var badDelegateInvoke = badDelegate.DefineMethod("Invoke", MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.NewSlot | MethodAttributes.HideBySig, typeof(int).MakeByRefType(), Type.EmptyTypes);
badDelegateInvoke.SetImplementationFlags(MethodImplAttributes.Runtime | MethodImplAttributes.Managed);
var badDelegateType = badDelegate.CreateType();
var method = module.DefineGlobalMethod("-", MethodAttributes.Public | MethodAttributes.Static, typeof(void), new[] { typeof(Delegate), typeof(Callback) });
var il = method.GetILGenerator();
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Castclass, badDelegate);
il.Emit(OpCodes.Callvirt, badDelegateInvoke);
il.Emit(OpCodes.Callvirt, typeof(Callback).GetMethod("Invoke"));
il.Emit(OpCodes.Ret);
module.CreateGlobalFunctions();
var callBadFunction = (CallBadFunction)Delegate.CreateDelegate(typeof(CallBadFunction), module.GetMethod("-"));
callBadFunction(badMethod.CreateDelegate(badDelegateType), (ref int i) =>
{
i++;
});
}
}
After compiling this program, use ILDASM to disassemble it, and replace BadMethod's definition by
.method private hidebysig static int32&
BadMethod() cil managed
{
ldsflda int32 Program::i
ret
}
This turns it into a function returning int32&, which the following code will then manage to call. The only location C# allows int32& types is in function parameters (ref int), so to make the result usable, I used a callback function, which gets passed the return value of BadMethod.
Thank you "hvd" for pointing me in the right direction. After some thought, here is what I came up with. It's simpler, but please tell me if you see a flaw.
var theirObj = new TheirClass();
var t = theirObj.GetType();
var fooBar = t.GetMethod("FooBar"); // Byte& FooBar()
DynamicMethod dm = new DynamicMethod(
"MyFooBar",
MethodAttributes.Public | MethodAttributes.Static,
CallingConventions.Standard,
typeof(IntPtr),
new Type[] { typeof(TheirClass) },
typeof(TheirClass),
true);
var ilg = dm.GetILGenerator();
ilg.Emit(OpCodes.Ldarg_0);
ilg.Emit(OpCodes.Call, foobar);
ilg.Emit(OpCodes.Ret);
var del = dm.CreateDelegate(typeof(Func<TheirClass,IntPtr>));
var ret = (IntPtr)del.DynamicInvoke(theirObject);
byte[] buf = new byte[theirObj.FooBarSize()]; //no need for reflection/IL here
// not sure about the following, it works, but should it be inside an "unsafe" section?
Marshal.Copy(ret, buf, 0, buf.Length);
I put a simple wrapper around the offending method and IL doesn't care that I treat the Byte& as an IntPtr. I'm still not sure about doing a Copy without an unsafe wrapper. But this is good for now.

Generate Eventhandler of unknown type while running

is it possible to generate an eventhandler while running?
I want to do something like that:
public bool addCallback(string name, Delegate Callback)
{
EventInfo ei = DataProxy.GetType().GetEvent(name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
if (ei == null)
return false;
ei.AddEventHandler(DataProxy, Callback);
//now I want to add an Eventhandler, which removes the Callback and this new Eventhandler itsself
return true;
}
(I am not 100% sure I understand what you are going to hook your generated event handler to from the example, but here's the easiest way I know of for creating an event handler)
Depends on your platform and trust level. The most flexible way of doing it is to use Emit to generate the method (see here).
However, I found a relatively easy to use and good alternative to be generated Linq expressions (here's the namespace help).
The idea is fairly simple:
Use the various Expression-derived classes you can see in the namespace to define what your callback is doing. In this case, you want to generate something that calls .RemoveEventHandler (I am guessing) on the ei instance (specifically, you will use the ConstantExpression to create a ref to your ei variable and to your Callback parameter and a MethodCallExpression to create a call to the RemoveDataHandler method).
Once you create the expression that does what you need, you need to create a delegate (Lambda) out of it (see here)
Almost done. You still need to compile the lambda, which you do by calling .Compile on the object you got from the previous step (see here)
Edit: This is a Windows console example of a dynamically generated delegate that removes itself. Note that WP7 Linq expression support is more limited than .NET 4.0 and so you will need to adjust it (make helper methods that will do some of the work and call them from the expression instead of what I did).
Edit2: BTW: The mechanism by which the lambda can remove itself, is to create another lambda that returns a local variable that is of that type. After creating the lambda, save it to the local variable and run the code (I am not sure if this would have worked without the secondary lambda)
Edit3: No - you have to use the delegate trick, otherwise, the constant gets "frozen" and will not update as you would want it to. So the code as is works.
public class MyEventArgs : EventArgs
{
}
public class EventContainer
{
public event EventHandler<MyEventArgs> MyEvent;
public void Fire()
{
Console.WriteLine("Firing");
if (MyEvent != null)
{
MyEvent(this, new MyEventArgs());
}
Console.WriteLine("Fired");
}
}
class Program
{
static void Main(string[] args)
{
EventContainer container = new EventContainer();
var adder = container.GetType().GetMethod("add_MyEvent");
var remover = container.GetType().GetMethod("remove_MyEvent");
object self = null;
Func<object> getSelf = () => self;
var block = Expression.Block(
// Call something to output to console.
Expression.Call(
null,
typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }),
Expression.Constant("Event called")),
// Now call the remove_Event method.
Expression.Call(
Expression.Constant(container), // passing the container as "this"
remover, // And the remover as the method info
Expression.Convert( // we need to cast the result of getSelf to the correct type to pass as an argument
Expression.Invoke( // For the parameter (what to convert), we need to call getSelf
Expression.Constant(getSelf)), // So this is a ref to getSelf
adder.GetParameters()[0].ParameterType) // finally, say what to convert to.
)
);
// Create a lambda of the correct type.
var lambda = Expression.Lambda(
adder.GetParameters()[0].ParameterType,
block,
Expression.Parameter(typeof(object)),
Expression.Parameter(typeof(MyEventArgs)));
var del = lambda.Compile();
// Make sure "self" knows what the delegate is (so our generated code can remove it)
self = del;
// Add the event.
adder.Invoke(container, new object[] { del });
// Fire once - see that delegate is being called.
container.Fire();
// Fire twice - see that the delegate was removed.
container.Fire();
}
}
public static bool addCallback(string name, Delegate Callback)
{
if (DataProxy == null)
GetDataProxy();
EventInfo ei = DataProxy.GetType().GetEvent(name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
if (ei == null)
return false;
ei.AddEventHandler(DataProxy, Callback);
Type handlerType = ei.EventHandlerType;
MethodInfo invokeMethod = handlerType.GetMethod("Invoke");
ParameterInfo[] parms = invokeMethod.GetParameters();
Type[] parmTypes = new Type[parms.Length];
for (int i = 0; i < parms.Length; i++)
{
parmTypes[i] = parms[i].ParameterType;
}
List<ParameterExpression> parameters = new List<ParameterExpression>();
foreach(Type t in parmTypes)
{
parameters.Add(System.Linq.Expressions.Expression.Parameter(t));
}
ConstantExpression eventInfo = System.Linq.Expressions.Expression.Constant(ei, typeof(EventInfo));
ConstantExpression eventCallback = System.Linq.Expressions.Expression.Constant(Callback, typeof(Delegate));
ConstantExpression dataProxy = System.Linq.Expressions.Expression.Constant(DataProxy, typeof(MAServiceClient));
MethodCallExpression call = System.Linq.Expressions.Expression.Call(eventInfo, ei.GetType().GetMethod("RemoveEventHandler"), dataProxy, eventCallback);
//add to Expression.Body the call, which removes the new Eventhandler itsself
ei.AddEventHandler(DataProxy, System.Linq.Expressions.Expression.Lambda(ei.EventHandlerType, call, parameters).Compile());
return true;
}
This is what my method looks like right now. There is just one step missing, where the new Eventhandler (created by System.Linq.Expressions.Expression.Lambda(ei.EventHandlerType, call, parameters).Compile()) removes itsself (see comment).
Thanks to Shahar Prish I came up with the following code:
using ex = System.Linq.Expressions;
using System.Linq.Expressions;
public static bool addCallback(string name, Delegate Callback)
{
if (DataProxy == null)
GetDataProxy();
EventInfo ei = DataProxy.GetType().GetEvent(name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
if (ei == null)
return false;
ei.AddEventHandler(DataProxy, Callback);
Type handlerType = ei.EventHandlerType;
MethodInfo removeMethod = ei.GetType().GetMethod("RemoveEventHandler");
MethodInfo invokeMethod = handlerType.GetMethod("Invoke");
ParameterInfo[] parms = invokeMethod.GetParameters();
Type[] parmTypes = new Type[parms.Length];
for (int i = 0; i < parms.Length; i++)
{
parmTypes[i] = parms[i].ParameterType;
}
List<ParameterExpression> parameters = new List<ParameterExpression>();
foreach(Type t in parmTypes)
{
parameters.Add(System.Linq.Expressions.Expression.Parameter(t));
}
Delegate self = null;
Func<Delegate> getSelf = () => self;
ConstantExpression eventInfo = ex.Expression.Constant(ei, typeof(EventInfo));
ConstantExpression eventCallback = ex.Expression.Constant(Callback, typeof(Delegate));
ConstantExpression dataProxy = ex.Expression.Constant(DataProxy, typeof(MAServiceClient));
MethodCallExpression removeCallback = ex.Expression.Call(eventInfo, removeMethod, dataProxy, eventCallback);
MethodCallExpression removeSelf = ex.Expression.Call(eventInfo, removeMethod, dataProxy, ex.Expression.Invoke(ex.Expression.Constant(getSelf)));
BlockExpression block = ex.Expression.Block(removeCallback, removeSelf);
LambdaExpression lambda = ex.Expression.Lambda(ei.EventHandlerType, block, parameters);
Delegate del = lambda.Compile();
self = del;
ei.AddEventHandler(DataProxy, del);
lambda = ex.Expression.Lambda(ei.EventHandlerType, block, parameters);
return true;
}
As I said before, this method should add the Eventhandler passed by Delegate Callback to the Event named string name of the static MAServiceClient DataProxy and remove it after it was called (and the Eventhandler which removes the Callback itsself).

Categories