System.Reflection.Emit:ref struct

问题描述

对于以下接口和结构:

internal interface IRecord<T> where T : struct
{
    ref T Values { get; }
}

public struct Entity
{
    public int Field1;
    ...
}

我想通过反射获得以下lambda表达式:

Expression<Func<IRecord<Entity>,int>> getter = x => x.Values.Field1;
Expression<Action<IRecord<Entity>,int>> setter = (x,value) => x.Values.Field1 = value;

很遗憾,该文件无法编译:{{1​​}}。似乎通过反射不支持cs8153: an expression tree lambda may not contain a call to a method,property,or indexer that returns by reference的任何成员。

所以我必须去ref struct生成以下访问器类:

System.Reflection.Emit

并通过反射获得以下lambda表达式:

public static class Accessor
{
    public static int GetField1(IRecord<Entity> record) => record.Values.Field1;
    public static void SetField1(IRecord<Entity> record,int value) => record.Values.Field1 = value;
    ...
}

这是我的代码,用于使用Expression<Func<IRecord<Entity>,int>> getter = x => Accessor.GetField1(x); Expression<Action<IRecord<Entity>,value) => Accessor.SetField1(x,value); 生成Accessor类:

System.Reflection.Emit

使用生成private static readonly ModuleBuilder ModuleBuilder = GetModuleBuilder(); private static ModuleBuilder GetModuleBuilder() { AssemblyName assemblyName = new AssemblyName("AccesstypeBuilder"); AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName,AssemblyBuilderAccess.Run); ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("Module"); return moduleBuilder; } public static Type BuildAccessorType(Type fieldValuesType) { TypeBuilder typeBuilder = ModuleBuilder.DefineType("Accessor",TypeAttributes.Public | TypeAttributes.Abstract | TypeAttributes.Sealed,typeof(object)); BuildAccessor(typeBuilder,typeof(Entity),"Field1",typeof(int)); return typeBuilder.CreateType(); } private static void BuildAccessor(TypeBuilder typeBuilder,Type fieldValuesType,string fieldName,Type dataType) { typeBuilder.DefineGetter(fieldValuesType,$"Get{fieldName}",dataType,fieldName); typeBuilder.Definesetter(fieldValuesType,$"Set{fieldName}",fieldName); } private static void DefineField(this TypeBuilder typeBuilder,Type dataType,string fieldName) { typeBuilder.DefineField(fieldName,FieldAttributes.Public); } private static Type recordtype(this Type fieldValuesType) { return typeof(IRecord<>).MakeGenericType(fieldValuesType); } private static MethodInfo FieldValues(this Type fieldValuesType) { var recordtype = fieldValuesType.recordtype(); var property = recordtype.GetProperty(nameof(IRecord<int>.Values)); return property.getmethod; } private static FieldInfo Field(this Type fieldValuesType,string fieldName) => fieldValuesType.GetField(fieldName,BindingFlags.Public | BindingFlags.Instance); private static void DefineGetter(this TypeBuilder typeBuilder,string methodName,string fieldName) { var method = typeBuilder.DefineMethod(methodName,MethodAttributes.Public | MethodAttributes.Static,new Type[] { fieldValuesType.recordtype() }); var methodBody = method.GetILGenerator(); methodBody.EmitGetter(fieldValuesType,fieldName); } private static void EmitGetter(this ILGenerator methodBody,string fieldName) { methodBody.Emit(OpCodes.Ldarg_0); methodBody.Emit(OpCodes.Callvirt,fieldValuesType.FieldValues()); methodBody.Emit(OpCodes.Ldfld,fieldValuesType.Field(fieldName)); methodBody.Emit(OpCodes.Stloc_0); methodBody.Emit(OpCodes.Ldloc_0); methodBody.Emit(OpCodes.Ret); } private static void Definesetter(this TypeBuilder typeBuilder,typeof(void),new Type[] { fieldValuesType.recordtype(),dataType }); var methodBody = method.GetILGenerator(); methodBody.EmitSetter(fieldValuesType,fieldName); } private static void EmitSetter(this ILGenerator methodBody,string fieldName) { methodBody.Emit(OpCodes.nop); methodBody.Emit(OpCodes.Ldarg_0); methodBody.Emit(OpCodes.Callvirt,fieldValuesType.FieldValues()); methodBody.Emit(OpCodes.Ldarg_1); methodBody.Emit(OpCodes.Stfld,fieldValuesType.Field(fieldName)); methodBody.Emit(OpCodes.Ret); } 类时,调用生成的getter时得到Accessor;并在调用生成的setter时获得InvalidProgramException: Common Language Runtime detected an invalid program.

我在做什么错?我花了整整一整天的时间,感到非常沮丧。任何帮助将不胜感激!

解决方法

已修复。

  1. IRecord<T>接口必须为public;

  2. 删除两行发出的操作码:

private static void EmitGetter(this ILGenerator methodBody,Type fieldValuesType,string fieldName)
{
    methodBody.Emit(OpCodes.Ldarg_0);
    methodBody.Emit(OpCodes.Callvirt,fieldValuesType.FieldValues());
    methodBody.Emit(OpCodes.Ldfld,fieldValuesType.Field(fieldName));
    //methodBody.Emit(OpCodes.Stloc_0); --removed
    //methodBody.Emit(OpCodes.Ldloc_0); --removed
    methodBody.Emit(OpCodes.Ret);
}