I am trying to dynamically create a delegate that returns a list of the values of all properties defined on an object, using reflection emit in C#.
I have started with the examples given here: https://learn.microsoft.com/en-us/previous-versions/dotnet/netframework-4.0/exczf7b9(v=vs.100)
Then, I have used LinqPad to view the IL for the code I want to emit. E.g., for this code:
void Main()
{
new Zap{ Name = "1", Value = 2 }.Test();
}
public class Zap
{
public string Name { get; set; }
public int Value { get; set; }
public IEnumerable<object> Test()
{
return new List<object> {Name, Value}; // This is what I want to do...
}
}
I got this IL code for the Test()
method:
But when I run this code, I get a System.InvalidProgramException: 'Common Language Runtime detected an invalid program.'
exception:
new Target().DoMagic();
public class Target
{
delegate List<object> ListProps();
public string MyProperty1 { get; set; } = "zap";
public int MyProperty2 { get; set; } = 77;
public void DoMagic()
{
Type[] methodArgs = { };
DynamicMethod listPropsMethod = new DynamicMethod("ListProps", typeof(List<object>), methodArgs, typeof(Target).Module);
ConstructorInfo? constructor = typeof(List<object>).GetConstructor(new Type[0]);
MethodInfo? addMethod = typeof(List<object>).GetMethod("Add");
ILGenerator il = listPropsMethod.GetILGenerator();
il.Emit(OpCodes.Newobj, constructor);
il.Emit(OpCodes.Dup);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Call, this.GetType().GetProperty(nameof(MyProperty1), BindingFlags.Instance | BindingFlags.Public).GetGetMethod());
il.Emit(OpCodes.Callvirt, addMethod);
il.Emit(OpCodes.Dup);
il.Emit(OpCodes.Ldarg_0, this.GetType().GetProperty(nameof(MyProperty2), BindingFlags.Instance | BindingFlags.Public).GetGetMethod());
il.Emit(OpCodes.Box);
il.Emit(OpCodes.Callvirt, addMethod);
il.Emit(OpCodes.Ret);
var value = listProps();
Console.WriteLine("Result : " + JsonSerializer.Serialize(value));
}
}
I probably miss a lot of details (e.g., I don't see how calling methods would know which instances to use...), but if someone can shed some light on how I can make this work, I would be grateful.
Also, will invoking the generated delegate created with emit
be as fast as writing the first code snippet and compiling it? I am troubled by the reflection code I use to get the method/property information, and I wonder if this might cause some performance issues.
I think this should work:
delegate List<object> ListProps(Target obj);
and:
Type[] methodArgs = { typeof(Target) };
DynamicMethod listPropsMethod = new DynamicMethod("ListProps", typeof(List<object>), methodArgs, typeof(Target).Module);
ConstructorInfo? constructor = typeof(List<object>).GetConstructor(new Type[0]);
MethodInfo? addMethod = typeof(List<object>).GetMethod("Add");
ILGenerator il = listPropsMethod.GetILGenerator();
il.Emit(OpCodes.Newobj, constructor);
il.Emit(OpCodes.Dup);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Callvirt, this.GetType().GetProperty(nameof(MyProperty1), BindingFlags.Instance | BindingFlags.Public).GetGetMethod());
il.Emit(OpCodes.Callvirt, addMethod);
il.Emit(OpCodes.Dup);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Callvirt, this.GetType().GetProperty(nameof(MyProperty2), BindingFlags.Instance | BindingFlags.Public).GetGetMethod());
il.Emit(OpCodes.Box, typeof(int));
il.Emit(OpCodes.Callvirt, addMethod);
il.Emit(OpCodes.Ret);
var listProps = (ListProps)listPropsMethod.CreateDelegate(typeof(ListProps));
Console.WriteLine("Result : {0}", string.Join(", ", listProps(this)));
changes:
ldarg.0
box
ldarg.0
and call
/ callvirt
callvirt
on instance methods unless you really know why