.NET Core中延遲單例另一種寫法【.NET Core和.NET Framework的beforefieldinit差異】


1.BeforeFieldInit是什么

   前段時間在反編譯代碼時無意間看到在類中有一個BeforeFieldInit特性,處於好奇的心態查了查這個特性,發現這是一個關於字段初始化時間的特性【提前初始化字段】,下面先來看一下這個特性在.net framework中的作用

class Foo
{
   public static String x = GetStr("初始化 Foo 靜態成員字段");
   public static String GetStr(String str)
  {
       Console.WriteLine(str);
       return str;
  }
}

   在上面Foo類中只定義了一個靜態字段x和一個靜態方法GetStr的方法,在這里需要關注的是靜態字段x的初始化時機

static void Main(string[] args)
{
      Console.WriteLine("Main方法開始");
      Foo.GetStr("手動調用Foo.GetSring()方法");
      String y = Foo.x;
}

  在Main中簡單的調用靜態方法和靜態字段,我們知道靜態字段的賦值是在靜態構造函數中進行的,那么輸出順序應該是 “Main方法開始”,”初始化Foo靜態成員字段“,”手動調用Foo.GetString()方法“,但是真的是這樣嗎,答案是錯的

  可以看到靜態成員字段的初始化是在最開始,那么為什么會這樣呢,我們將代碼反編譯IL后會發現在類中具有一個beforefieldinit特性,

.class private auto ansi beforefieldinit BeoreFieldInitTest2.Foo
    extends [mscorlib]System.Object
{
} // end of class BeoreFieldInitTest2.Foo

   那么BeforeFieldInit是什么,我找到了一篇文章有對BeforeFieldInit的詳細講解,在這里也不過多介紹,

2.取消BeforeFieldInit加載

  那么該怎么取消beforefieldinit特性呢,其實很簡單,只需要在類中加入一個靜態構造函數即可

class Foo
{
     public static string x = GetStr("初始化 Foo 靜態成員字段");
//空的靜態構造函數
static Foo(){}
public static String GetStr(String str) { Console.WriteLine(str); return str; } }

  然后此時輸入就如我們所猜測那樣

 並且反編譯可以看到IL代碼也取消了beforefieldinit特性

.class private auto ansi BeoreFieldInitTest2.Foo
    extends [mscorlib]System.Object
{
} // end of class BeoreFieldInitTest2.Foo

  下面就該進入正題,來看看.NET Core中不一樣的BeforeFieldInit  

3.BeforeFieldInit在.NET Core 中的差異

  將最開始的代碼在.NET Core中跑一跑會發現跟.NET Framework不一樣的操作

class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Main方法開始");
            Foo.GetStr("手動調用Foo.GetSring()方法");
            String y = Foo.x;
        }
    }
    class Foo
    {
        public static string x = GetStr("初始化 Foo 靜態成員字段");
        public static String GetStr(String str)
        {
            Console.WriteLine(str);
            return str;
        }
    }

 

  可以看到在.NET Core並沒有像.NET Framework那樣進行提前加載,並且加載貌似還延遲了,反編譯代碼可以看到beforefieldinit特性還在Foo類上

.class private auto ansi beforefieldinit BeforeFieldInitTest.Foo
    extends [System.Runtime]System.Object
{
} // end of class BeforeFieldInitTest.Foo

    那么在.NET Core加入靜態構造函數會怎么呢?懷着各種疑惑進行測試

class Program
{
     static void Main(string[] args)
     {
          Console.WriteLine("Main方法開始");
          Foo.GetStr("手動調用Foo.GetSring()方法");
          String y = Foo.x;
      }
 }
class Foo
{
      public static string x = GetStr("初始化 Foo 靜態成員字段");
      //空的靜態構造函數
      static Foo() { }
      public static String GetStr(String str)
      {
          Console.WriteLine(str);
          return str;
      }
}

    可以看到.NET Core中加入靜態構造函數以后輸出跟.NET Framework一致,也就說可以猜測.NET Core運行時對beforefieldinit特性進行了優化,當然這也只是我的猜測

4.利用.NET Core中beforefieldinit實現的單例

   在.NET Framework中我們都是使用Lazy<>類來創建延遲加載單例,但是我們可以看到在.NET Core中beforefieldinit是延遲加載的,所以我們直接可以使用此方法來創建延遲安全單例,

class Program
{
    static void Main(string[] args)
    {
         Console.WriteLine("Main方法開始");
         Foo.GetStr("手動調用Foo.GetSring()方法");
         Console.WriteLine("我是分隔符");
          Console.WriteLine("我是分隔符");
          var foo= Foo.CreateInstance;
     }
}
class Foo
{
     public static Foo CreateInstance { get;  } = new Foo();
     private Foo()
     {
         Console.WriteLine("創建了Foo實例");
     }
     public static String GetStr(String str)
     {
         Console.WriteLine(str);
         return str;
     }
 }

  運行結果可以看到創建實例被延遲了,

  當然,這種創建單例也是有缺點的,當類中還有其它靜態字段或屬性時,並且在外部進行了調用,那么此時也會初始化此屬性

class Program
{
     static void Main(string[] args)
     {
         Console.WriteLine("Main方法開始");
         Foo.GetStr("手動調用Foo.GetSring()方法");
         var y = Foo.x;//調用靜態字段/屬性
         Console.WriteLine("我是分隔符");
         Console.WriteLine("我是分隔符");
         var foo= Foo.CreateInstance;
     }
 }
 class Foo
 {
     public static string x = GetStr("初始化 Foo 靜態成員字段"); //加入了靜態字段或屬性
     //public static String X { get; set; } = GetStr("初始化 Foo 靜態成員字段");
     public static Foo CreateInstance { get;  } = new Foo();
     private Foo()
     {
         Console.WriteLine("創建了Foo實例");
     }
     public static String GetStr(String str)
     {
         Console.WriteLine(str);
         return str;
     }
}

   也就是說在.NET Core中beforfieldinit特性時當有一個靜態變量被使用時就初始化所有靜態變量

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM