[英]Reflection GetValue of static field with circular dependency returns null
注意:以下代碼實際上可以正常工作,但顯示了我自己的解決方案中失敗的方案。 有關更多信息,請參閱本文的底部。
有了這些課程:
public class MainType {
public static readonly MainType One = new MainType();
public static readonly MainType Two = SubType.Two;
}
public sealed class SubType : MainType {
public new static readonly SubType Two = new SubType();
}
得到One
和Two
欄:
List<FieldInfo> fieldInfos = typeof(MainType)
.GetFields(BindingFlags.Static | BindingFlags.Public)
.Where(f => typeof(MainType).IsAssignableFrom(f.FieldType))
.ToList();
最后,得到他們的價值觀:
List<MainType> publicMainTypes = fieldInfos
.Select(f => (MainType) f.GetValue(null))
.ToList();
在LinqPad或帶有上述代碼的簡單單元測試類中,一切正常。 但是在我的解決方案中,我有一些想要處理這些字段的所有實例的單元測試, GetValue
可以正常返回父類型的字段,但是在父字段應該具有子類型的實例的情況下,它們總是會給出null
! (如果發生在這里,最終列表將是{ One, null }
而不是{ One, Two }
。)測試類與兩種類型(每個都在他們自己的文件中)處於不同的項目中,但我暫時一切都公開了。 我已經刪除了一個斷點,並檢查了我可以檢查的所有內容,並在Watch表達式中完成了fieldInfos[1].GetValue(null)
的等效操作,並且它確實返回null,盡管有一條線在我的主類中, MainType
上面的MainType
的第二個完全一樣。
怎么了? 如何獲取子類型字段的所有值? 他們甚至可以在沒有錯誤的情況下返回null?
根據理論,由於某些原因,由於通過反射訪問,子類型的類不是靜態構造的,我試過
System.Runtime.CompilerServices.RuntimeHelpers
.RunClassConstructor(typeof(SubType).TypeHandle);
在開始之前的頂部,但它沒有幫助( SubType
是我的項目中的實際子類型)。
我會繼續試圖在一個簡單的案例中重現這一點,但我暫時沒有想法。
附加信息
在一堆擺弄之后,代碼開始工作了。 現在它不再起作用了。 我正在努力復制觸發代碼開始工作的內容。
注意:在Visual Studio 2015中使用C#6.0定位.Net 4.6.1。
問題再現可用
您可以通過在github上下載這個問題的最小工作示例來玩我的場景的工作(失敗)修剪版本。
調試單元測試。 發生異常時,直到您到達GlossaryHelper.cs的第20行,並且可以在Locals
選項卡中看到GetGlossaryMembers
的返回值。 您可以看到索引3到12為空。
問題
該問題與Reflection無關,而是兩個靜態字段初始值設定項之間的循環依賴關系及其執行順序。
請考慮以下代碼段:
var b = MainType.Two;
var a = SubType.Two;
Debug.Assert(a == b); // Success
現在讓我們交換前兩行:
var a = SubType.Two;
var b = MainType.Two;
Debug.Assert(a == b); // Fail! b == null
那么這里發生了什么? 讓我們來看看:
SubType.Two
靜態字段。 SubType
的構造函數。 SubType
繼承自MainType
,因此MainType
構造函數也執行並觸發MainType
靜態初始化。 MainType.Two
字段靜態初始化程序正在嘗試訪問SubType.Two
。 由於靜態初始化程序只執行一次,而SubType.Two
的執行程序已經執行(好吧,不是真的,它當前正在執行,但被認為是),它只返回當前字段值(此時為null
),然后存儲在MainType.Two
,並將由該字段的進一步訪問請求返回。 簡而言之,這種設計的正確工作實際上取決於外部訪問字段的順序,因此它有時可以工作,有時不工作也就不足為奇了。 不幸的是,這是你無法控制的。
怎么修
如果可能,請避免此類靜態字段依賴性。 請改用靜態只讀屬性 。 它們為您提供完全控制,並允許您消除字段重復(目前您有2個不同的字段,其中包含一個相同的值)。
這是沒有這些問題的等效設計(使用C#6.0):
public class MainType
{
public static MainType One { get; } = new MainType();
public static MainType Two => SubType.Two;
}
public sealed class SubType : MainType
{
public new static SubType Two { get; } = new SubType();
}
當然這需要更改您的反射代碼以使用GetProperties
而不是GetFields
,但我認為這是值得的。
更新:解決此問題的另一種方法是將靜態字段移動到嵌套的抽象容器類:
public class MainType
{
public abstract class Fields
{
public static readonly MainType One = new MainType();
public static readonly MainType Two = SubType.Fields.Two;
}
}
public sealed class SubType : MainType
{
public new abstract class Fields : MainType.Fields
{
public new static readonly SubType Two = new SubType();
}
}
現在兩個測試都成功完成:
var a = SubType.Fields.Two;
var b = MainType.Fields.Two;
Debug.Assert(a == b); // Success
和
var b = MainType.Fields.Two;
var a = SubType.Fields.Two;
Debug.Assert(a == b); // Success
這是因為容器類除了包含在內之外與靜態字段類型無關,因此它們的靜態初始化是獨立的。 此外,雖然它們使用繼承,但它們永遠不會被實例化(因為是abstract
),因此沒有基礎構造函數調用引起的副作用。
我有類似的問題。 問題是我實現了一個類的靜態字段,並通過反射嘗試使用它的值。 它在我的調試解決方案中運行良好,但在我的生產環境中無效。 問題是Release配置中的編譯器發現從不使用此靜態方法並刪除無法訪問的代碼。 要解決此問題,您應該刪除Optimize code flag。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.