簡體   English   中英

具有循環依賴性的靜態字段的反射GetValue返回null

[英]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();
}

得到OneTwo欄:

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

那么這里發生了什么? 讓我們來看看:

  1. 代碼首次嘗試訪問SubType.Two靜態字段。
  2. 靜態初始化程序觸發並執行SubType的構造函數。
  3. 由於SubType繼承自MainType ,因此MainType構造函數也執行並觸發MainType靜態初始化。
  4. 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM