简体   繁体   English

System.Reflection.Emit:ref struct 的字段或属性

[英]System.Reflection.Emit: field or property of ref struct

For the following interface and struct:对于以下接口和结构:

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

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

I would like get the following lambda expression via reflection:我想通过反射获得以下 lambda 表达式:

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

Unfortunately this doesn't compile: cs8153: an expression tree lambda may not contain a call to a method, property, or indexer that returns by reference .不幸的是,这不能编译: cs8153: an expression tree lambda may not contain a call to a method, property, or indexer that returns by reference It seems get any member of ref struct is not supported via reflection.似乎通过反射不支持获取ref struct任何成员。

So I have to go for System.Reflection.Emit to generate the following accessor class:所以我必须去System.Reflection.Emit生成以下访问器类:

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;
    ...
}

and get the following lambda expression via reflection:并通过反射得到以下 lambda 表达式:

Expression<Func<IRecord<Entity>, int>> getter = x => Accessor.GetField1(x);
Expression<Action<IRecord<Entity>, int>> setter = (x, value) => Accessor.SetField1(x, value);

Here is my code to generate the Accessor class using System.Reflection.Emit :这是我使用System.Reflection.Emit生成Accessor类的代码:

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}", dataType, fieldName);
}

private static void DefineField(this TypeBuilder typeBuilder, Type dataType, string fieldName)
{
    typeBuilder.DefineField(fieldName, dataType, 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, Type fieldValuesType, string methodName, Type dataType, string fieldName)
{
    var method = typeBuilder.DefineMethod(methodName, MethodAttributes.Public | MethodAttributes.Static, dataType, new Type[] { fieldValuesType.RecordType() });
    var methodBody = method.GetILGenerator();
    methodBody.EmitGetter(fieldValuesType, fieldName);
}

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);
    methodBody.Emit(OpCodes.Ldloc_0);
    methodBody.Emit(OpCodes.Ret);
}

private static void DefineSetter(this TypeBuilder typeBuilder, Type fieldValuesType, string methodName, Type dataType, string fieldName)
{
    var method = typeBuilder.DefineMethod(methodName, MethodAttributes.Public | MethodAttributes.Static, typeof(void), new Type[] { fieldValuesType.RecordType(), dataType });
    var methodBody = method.GetILGenerator();
    methodBody.EmitSetter(fieldValuesType, fieldName);
}

private static void EmitSetter(this ILGenerator methodBody, Type fieldValuesType, 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);
}

When consuming the generated Accessor class, I get InvalidProgramException: Common Language Runtime detected an invalid program.使用生成的Accessor类时,我收到InvalidProgramException: Common Language Runtime detected an invalid program. when invoking the generated getter;调用生成的 getter 时; and get System.MethodAccessException: Attempt by method 'Accessor.SetField0(IRecord`1<Entity>, Int32)' to access IRecord`1<Entity>.get_FieldValues()' failed.并获取System.MethodAccessException: Attempt by method 'Accessor.SetField0(IRecord`1<Entity>, Int32)' to access IRecord`1<Entity>.get_FieldValues()' failed. when invoking the generated setter.调用生成的 setter 时。

What am I doing wrong?我究竟做错了什么? I've spent whole day for this and is pretty frustrated.我为此花了一整天,非常沮丧。 Any help will be very much appreciated!任何帮助将不胜感激!

Fixed.固定的。

  1. The IRecord<T> interface must be public ; IRecord<T>接口必须是public

  2. Remove two lines of opcode emitting:删除两行发出的操作码:

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);
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM