【问题标题】:System.Reflection.Emit: field or property of ref structSystem.Reflection.Emit:ref struct 的字段或属性
【发布时间】:2020-10-15 12:43:12
【问题描述】:

对于以下接口和结构:

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;

不幸的是,这不能编译: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 生成以下访问器类:

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

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

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

这是我使用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);
}

使用生成的Accessor 类时,调用生成的getter 时得到InvalidProgramException: Common Language Runtime detected an invalid program.;并在调用生成的setter时得到System.MethodAccessException: Attempt by method 'Accessor.SetField0(IRecord`1&lt;Entity&gt;, Int32)' to access IRecord`1&lt;Entity&gt;.get_FieldValues()' failed.

我做错了什么?我为此花了一整天,非常沮丧。任何帮助将不胜感激!

【问题讨论】:

  • 你是否已经完成了正确生成代码的标准舞蹈,也就是说,如果你以明显的方式编译代码(通过ildasm 或类似sharplab.io),那么检查你得到的IL,然后看看你需要生成什么?
  • 您的代码不会按原样编译,顺便说一句,因为您在生成的代码中使用FieldValues,但在定义中使用Values。使示例自包含简化了在家中的人的后续操作。对于您的故障排除(我也看不到任何明显的错误,但ref 无疑是一件棘手的事情),您可以改为发送到程序集并保存它,然后将其反编译与“正确的”IL 进行比较。某些特殊标志或修饰符字节或其他可能丢失。
  • @JeroenMostert 谢谢。我已经编辑了问题并消除了编译错误。我正在制作一个独立的控制台应用程序来保存生成的程序集,以便我可以使用ildasm 来检查它。
  • IRecord&lt;T&gt; 接口从internal 更改为public 可使setter 正常工作。发出的 getter 代码一定有问题。还在调查中……
  • 哦,我完全错过了。 :-P 是的,请记住,生成的代码在作为动态模块/程序集生成时需要遵守可见性规则(而不是作为动态方法生成时)。它无法访问internal types/members,因为它不在生成代码的程序集中。

标签: c# reflection emit ref-struct


【解决方案1】:

固定。

  1. IRecord&lt;T&gt;接口必须是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);
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-06-19
    • 2012-06-05
    • 2013-05-20
    • 1970-01-01
    • 1970-01-01
    • 2019-06-04
    • 2012-03-20
    • 2017-02-21
    相关资源
    最近更新 更多