问题描述
对于以下接口和结构:
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.
。
我在做什么错?我花了整整一整天的时间,感到非常沮丧。任何帮助将不胜感激!
解决方法
已修复。
-
IRecord<T>
接口必须为public
; -
删除两行发出的操作码:
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);
}