【问题标题】:Setting the value of a static field using Reflection.Emit fails in Unity在 Unity 中使用 Reflection.Emit 设置静态字段的值失败
【发布时间】:2017-01-18 09:49:51
【问题描述】:

我正在尝试使用 Reflection.Emit 设置静态字段的值(我无法访问 .NET 4 的 Expression.Assign,因为我坚持使用 Unity 的 .NET 3.5)。

我目前的代码如下:

public Action<TTarget, TField> GetSetter<TTarget, TField>(FieldInfo fieldInfo)
{
    DynamicMethod setterMethod = new DynamicMethod
    (
        "setter",
        typeof(void),
        new Type[] { typeof(TTarget), typeof(TField) },
        typeof(TTarget)
    );

    var setterIL = setterMethod.GetILGenerator();

    if (fieldInfo.IsStatic)
    {
        setterIL.Emit(OpCodes.Ldnull);
    }
    else
    {
        setterIL.Emit(OpCodes.Ldarg_0);
    }

    setterIL.Emit(OpCodes.Ldarg_1);
    setterIL.Emit(OpCodes.Stfld, fieldInfo);
    setterIL.Emit(OpCodes.Ret);

    return (Action<TTarget, TField>)setterMethod.CreateDelegate(typeof(Action<TTarget, TField>));
}

然后,我使用以下方法调用 setter:

public class Static
{
    public static int x;
}

var fieldInfo = typeof(Static).GetField("x");

var setter = GetSetter<Static, int>(fieldInfo);

setter.Invoke(null, 123);

我收到此错误消息:

NullReferenceException:对象引用未设置为对象的实例 (包装器动态方法) 设置器 (...,int)

我认为加载 null 作为第一个参数 (Ldnull 操作码) 可以解决它,但它似乎不起作用。我做错了什么?

更新:似乎只有在代码从 Unity(最新,5.5.0p4)中运行时才会触发异常。在从 Visual Studio 创建的 .NET 3.5 控制台应用程序中,没有问题。 Unity 的 Mono 编译器会不会有问题?

这里是通过 Unity 中的 Tools &gt; Debug IL 菜单项进行测试的完整代码。

using System;
using System.Reflection;
using System.Reflection.Emit;
using UnityEditor;

class Program
{
    public static Action<TTarget, TField> GetSetter<TTarget, TField>(FieldInfo fieldInfo)
    {
        DynamicMethod setterMethod = new DynamicMethod
        (
            "setter",
            typeof(void),
            new Type[] { typeof(TTarget), typeof(TField) },
            typeof(TTarget)
        );

        var setterIL = setterMethod.GetILGenerator();

        setterIL.Emit(OpCodes.Ldarg_0);
        setterIL.Emit(OpCodes.Ldarg_1);
        setterIL.Emit(OpCodes.Stfld, fieldInfo);
        setterIL.Emit(OpCodes.Ret);

        return (Action<TTarget, TField>)setterMethod.CreateDelegate(typeof(Action<TTarget, TField>));
    }

    public class Static
    {
        public static int x;
    }

    [MenuItem("Tools/Debug IL")]
    static void Debug()
    {
        var fieldInfo = typeof(Static).GetField("x");

        var setter = GetSetter<Static, int>(fieldInfo);

        setter.Invoke(null, 123);

        Debug.Log("Static field assignment succeeded.");
    }
}

【问题讨论】:

  • 你确定这是准确的代码吗? GetSetter&lt;Static, int&gt; 不应该编译 - 你不能使用静态类型作为类型参数
  • 另外,您不需要检查该字段是否是静态的。由于您的方法要求目标,(并且您提供它:Invoke(null, 123)) - 您应该始终简单地发出 Ldarg_0。这也会导致有人写 Invoke(notnullinstance, 123) 的问题 - 应该 引发错误,但会默默地将 123 设置为静态字段。
  • 你说得对,类不是静态的,我急忙整理了一个简化的例子。根据MSDN,你又是对的,Ldarg_0 应该可以工作,但它没有。
  • 在这种情况下,我无法复制它(复制并粘贴您的代码而不进行更改)并且它运行良好。您正在运行什么框架版本?您确定该字段确实是静态的吗?
  • 我刚刚复制并粘贴了我提供的示例代码,我得到了错误。我在 Unity 中使用 .NET 3.5。

标签: c# unity3d reflection.emit


【解决方案1】:

改用OpCodes.Stsfld(设置static字段):

if (fieldInfo.IsStatic)
{
    setterIL.Emit(OpCodes.Ldarg_0);
    setterIL.Emit(OpCodes.Stsfld, fieldInfo);
    setterIL.Emit(OpCodes.Ret);
}
else
{
    setterIL.Emit(OpCodes.Ldarg_0);
    setterIL.Emit(OpCodes.Ldarg_1);
    setterIL.Emit(OpCodes.Stfld, fieldInfo);
    setterIL.Emit(OpCodes.Ret);
}

.NET 运行时可能比 Mono 运行时在幕后更宽松(即:它允许 Stfld 甚至对于静态字段,只是忽略第一个参数,而 Mono 不允许),这可以解释为什么问题仅发生在 Unity 中。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-05-29
    • 1970-01-01
    • 2011-01-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多