2017-01-17 47 views
2

我試圖設置一個靜態字段的值使用Reflection.Emit(我沒有訪問.NET 4的Expression.Assign,因爲我堅持Unity的.NET 3.5)。使用Reflection.Emit設置靜態字段的值在Unity中失敗

我當前的代碼如下:

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

然後,我調用使用二傳手:

public class Static 
{ 
    public static int x; 
} 

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

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

setter.Invoke(null, 123); 

我收到此錯誤信息:

的NullReferenceException:對象引用未設置爲對象的實例 (包裝器動態方法)setter(...,int)

我認爲加載null作爲第一個參數(Ldnull操作碼)會解決它,但它似乎並沒有工作。我究竟做錯了什麼?

更新:它似乎只有在代碼從Unity內部運行(最新,5.5.0p4)時才觸發異常。在從Visual Studio創建的.NET 3.5控制檯應用程序中,沒有問題。 Unity的Mono編譯器會有問題嗎?

以下是在Unity中從Tools > 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."); 
    } 
} 
+1

你確定這是確切的代碼嗎? 'GetSetter '不應該編譯 - 你不能使用靜態類型作爲類型參數 – Rob

+2

另外,你不需要檢查該字段是否是靜態的。由於你的方法要求目標,(你提供它:'Invoke(null,123)') - 你應該總是發出'Ldarg_0'。這也會導致有人寫'Invoke(notnullinstance,123)' - 哪個*應該拋出一個錯誤,但是會靜靜地將'123'設置爲靜態字段。 – Rob

+0

你說得對,班上不是靜態的,我匆匆做了一個簡單的例子。根據[MSDN](https://msdn.microsoft.com/en-us/library/aya2tw8f(v = vs.100).aspx),你又是對的,'Ldarg_0'應該可以工作,但它不會'噸。 – Lazlo

回答

2

得到它的工作與OpCodes.Stsfld(設置靜態場)代替:

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運行庫可能比場景(即後面的單聲道運行時更爲寬鬆:它允許Stfld甚至對於靜態字段,只是忽略第一個參數,而單聲道不),這可以解釋爲什麼這個問題只發生在Unity中。