熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> .NET編程 >> 正文

.Net Framework框架源碼學習

2022-06-13   來源: .NET編程 

  Singleton模式由於其實現相對簡單所以號稱設計模式中最簡單的模式
  但是static通常會給你造成一些障礙不信啊那你就看看吧而且還有幾個Effective C#條款 :) 希望不會讓你失望 
   
           該篇並沒有涉及到Net Framework源碼就算是掛羊頭賣狗肉吧希望延續上篇的高質量
  讓我們先來寫個簡單的SqlHelper吧封裝SQL數據庫的操作
  
  using System;
  using SystemData;
  using SystemDataSqlClient;
  
  namespace APeng
  {
   public class SqlHelper
   {
   private string m_connString = Data Source=(local); +
   User ID=sa;Password=password;Initial Catalog=discuz;Pooling=true;
   //Sql數據庫連接字符串
  
   public SqlDataReader ExecuteReader(CommandType cmdType string cmdText params SqlParameter[] cmdParms) {
   SqlCommand cmd = new SqlCommand();
   SqlConnection conn = new SqlConnection(m_connString);
  
   try
   {
   PrepareCommand(cmd conn cmdType cmdText cmdParms);
   SqlDataReader rdr = cmdExecuteReader(CommandBehaviorCloseConnection);
   cmdParametersClear();
   return rdr;
   }
   catch (Exception ex)
   {
   connClose();
   connDispose();
   throw ex;
   }
   }
  
   private void PrepareCommand(SqlCommand cmd SqlConnection conn CommandType cmdType string cmdText SqlParameter[] cmdParms)
   {
  
   if (connState != ConnectionStateOpen)
   connOpen();
  
   cmdConnection = conn;
   cmdCommandText = cmdText;
   cmdCommandType = cmdType;
  
   if (cmdParms != null)
   {
   foreach (SqlParameter parm in cmdParms)
   cmdParametersAdd(parm);
   }
   }
   }
  }
  
  這段代碼大家應該很熟悉了接下來讓我們來使用它
  
  
  using System;
  using SystemData;
  using SystemDataSqlClient;
  
  namespace APeng
  {
   class Program
   {
   static void Main(string[] args)
   {
   SqlHelper helper = new SqlHelper();
   string cmdText = select fidname from dnt_forums;
   using (SqlDataReader reader = helperExecuteReader(CommandTypeText cmdText null))
   {
   while (readerRead())
   {
   ConsoleWriteLine(編號: + reader[fid]);
   ConsoleWriteLine(名稱: + reader[name]);
   }
   }
  
   ConsoleRead();
   }
  
  
   }
  } 
   
   
   
           程序正常輸出: 編號: 名稱:版塊 編號: 名稱:版塊
  很簡單不是嘛
  
  接下來我們將要優化這個SqlHelper
  一) 將SqlHelper中的private string m_connString = 修改成 private static readonly string m_connString =  
   
           這個修改是否有必要呢? (如果沒能給我帶來什麼好處我為什麼要修改呢所以你得說服我!) 
   
           小菜先在A地方實例化一個SqlHelper
  SqlHelper helper = new SqlHelper();
  那麼會有如下構造過程:
  為數據成員m_connString分配內存空間此時空間存儲數據為null
   (如果是值類型如intfloatdouble等空間存儲數據為如果是引用類型空間存儲數據為null下面還會詳細說明)
  執行數據成員m_connString的初始化語句也就是上面的private string m_connString =
   (那麼現在空間存儲數據為)
  執行SqlHelper的構造函數
  
  小菜然後在B地方又實例化一個SqlHelper
  SqlHelper helper = new SqlHelper();
  那麼會有如下構造過程:
  為數據成員m_connString分配內存空間此時空間存儲數據為null
  執行數據成員m_connString的初始化語句也就是上面的private string m_connString =
   (那麼現在空間存儲數據為)
  執行SqlHelper的構造函數 
   
           噢有沒有搞錯啊怎麼一直為m_connString分配內存空間
  而且老是把m_connString空間存儲數據置為相同的
  該死的你就不能聰明點做一次就夠了(浪費我們寶貴的時間和寶貴的內存資源) 
   
           唉看來我們得自己動手優化了怎麼優化呢?
  等等小菜剛才說什麼來著?修改成private static readonly string m_connString =
  那它能改變這種狀況嗎? 
   
           小菜先在A地方實例化一個SqlHelper
  SqlHelper helper = new SqlHelper();
  那麼會有如下構造過程:
  為靜態數據成員m_connString分配內存空間此時空間存儲數據為null
   (如果是值類型如intfloatdouble等空間存儲數據為如果是引用類型空間存儲數據為null下面還會詳細說明)
  執行靜態數據成員m_connString的初始化語句也就是上面的private static readonly string m_connString =
  (那麼現在空間存儲數據為)
  執行SqlHelper的構造函數 
   
           小菜然後在B地方又實例化一個SqlHelper
  SqlHelper helper = new SqlHelper();
  那麼會有如下構造過程:
  執行SqlHelper的構造函數 
   
    

  看來真不錯變聰明了只分配了一次m_connString的內存空間只初始化了一次m_connString
  看來多虧了static 
   
           注意:這裡應該引起你的關注
  有一些朋友的代碼中時常出現為值類型成員賦為引用類型賦null
  public class Person//人類
  {
   private int _age = ;//年齡
  }
  或者
  public class Person//人類
  {
   private Address _address = null;//地址對象
  } 
           這其實是無必要的和上面new SqlHelper()的構造過程一樣在分配數據成員的內存空間時便會為值類型成員賦為引用類型賦null.如果我們顯示的賦值的話不但沒有任何幫助反而會增加指令的操作影響效率.
  Effective C# 中有介紹 
   
           其實上面主要的知識點是對象的構造過程讓我們來復習一下吧
  第一種:
  1.當我們調用類裡的靜態方法時如果類裡面的靜態成員還未初始化那麼這個類的所有靜態成員依據在類裡面出現的次序初始化.
  2.為靜態成員分配內存空間此時空間存儲數據為或null
  3.執行靜態成員的初始化語句(也就是賦值語句)
  4.執行類的靜態構造函數
  
  很明顯這樣的話如果我們第二次調用類裡的靜態方法時4都不會被執行了. 
   
    

  第二種:
  1.當我們對類實例化的時候如果類裡面的靜態成員還未初始化那麼這個類的所有靜態成員依據在類裡面出現的次序初始化.
  2.為靜態成員分配內存空間此時空間存儲數據為或null
  3.執行靜態成員的初始化語句(也就是賦值語句)
  4.執行類的靜態構造函數
  5 為普通成員分配內存空間此時空間存儲數據為或null
  6.執行普通成員的初始化語句(也就是賦值語句)
  執行類的構造函數
  
  很明顯這樣的話如果我們第二次實例化類4也都不會被執行只會執行5
  
  二) 將public SqlDataReader ExecuteReader() 修改成 public static SqlDataReader ExecuteReader()
  修改不修改關鍵看什麼呢?
  如果該方法無需保持或變動跟對象有關的狀態則說明該方法與任何實例無關所以可設計成static方法
  我們的ExecuteReader()滿足上面條件無需操持對象有關狀態而且無需變動跟對象有關的狀態
  
  三) 將public class SqlHelper 修改為 public static class SqlHelper
  經過上面的修改後我們的SqlHelper已經是一個合適工具類它無需被實例化使用abstract無需被繼承使用sealed
  可是沒有public abstract sealed class SqlHelper 但有static 二者是等效的稱為靜態類
  
  Math類相信大家都用的很爽吧比如MathAbs()取絕對值等方法
  很明顯Math也是做為一個工具類所以在Net中也被設計成靜態類
  
  注意:有些朋友要說了那SqlHelper可不可以使用單件模式設計
  可以可是不合適做為一個工具類它根本無需被實例化一次都不要
  有些朋友要說了講單件模式講到哪裡去了都不知道但小菜覺得區分不好static單件模式是用不好的濫用誤用更是不在話下
  
  接下來就正式來說單件模式吧! (只允許實例化一次)
  )第一種單件模式
  public sealed class Singleton
  {
   private static readonly Singleton _instance = new Singleton();
  
   private Singleton()
   { }
  
   public static Singleton Instance
   {
   get
   {
   return _instance;
   }
   }
  
   public void DoSomething()
   {
   ConsoleWriteLine(做些事情);
   }
  }
  接下來我們來用用測試一下
  static void Main(string[] args)
  {
   Singleton s = SingletonInstance;
   Singleton s = SingletonInstance;
   sDoSomething();//做些事情
   sDoSomething();//做些事情
   ConsoleWriteLine(objectReferenceEquals(s s));//是否為相同實例true
  }
  恩不錯大家覺得上面的設計怎麼樣?
  其一: 如果很看注性能的話或者Singleton很浪費資源的話使用lazyinit會比較好一點當需要用時才初始化
   但通常上面的代碼是夠用的
  其二: 由於靜態成員的初始化時間很難控制所以如果是靜態引用類型的話放在靜態構造函數中初始化會更加適合
   這裡就用到了前面講到的對象構造順序如果不清楚的話建議拉到前面在看一下很重要
   這也是Effective C#中有介紹
  所以代碼修改為
  public sealed class Singleton
  {
   private static readonly Singleton _instance;
  
   private Singleton()
   { }
  
   static Singleton()
   {
   _instance = Singleton();
   }
  
   public static Singleton Instance
   {
   get
   {
   return _instance;
   }
   }
  
   public void DoSomething()
   {
   ConsoleWriteLine(做些事情);
   }
  }
  如果對性能不是太講究的話推薦該做法而且適合多線程
  
  二)第二種單件模式lazyinit
   public sealed class Singleton
   {
   private static Singleton _instance;
  
   private Singleton()
   { }
  
   public static Singleton Instance
   {
  get
  {
  if (_instance == null)
  {
  _instance = new Singleton();
  }
  return _instance;
  }
  }
 
  public void DoSomething()
  {
  ConsoleWriteLine(做些事情);
  }
  }
  代碼也很清晰但該單件模式只適用於單線程不適合與多線程
  為什麼呢?
  線程取SingletonInstance執行到第行if(_instance == null) 成立進入停下
  線程取SingletonInstance執行到第行if(_instance == null) 也同樣成立
   進入執行第行_instance = new Singleton() 設為obj 停下
  線程繼續執行同樣執行第行_instance = new Singleton() 設為obj
  obj與obj是不同已經不是單件是雙件了線程越多可能多件都有可能
  
  這樣的話很多朋友馬上會相到把 if(_instance==null){/*省略*/}鎖上不就ok了
  那就進入第三種單件模式
  
  三)第三種單件模式
  public sealed class Singleton
  {
   private static Singleton _instance;
   private static object _lockHelper = new object();
  
   private Singleton()
   { }
  
   public static Singleton Instance
   {
   get
   {
   lock (_lockHelper)
   {
   if (_instance == null)
   {
   _instance = new Singleton();
   }
   }
   return _instance;
   }
   }
  
   public void DoSomething()
   {
   ConsoleWriteLine(做些事情);
   }
  }
  看來該單件模式支持多線程但看來並不是太聰明
  
  如果_instance已經被初始化然後每次線程進入還是需要同步很明顯效率下降
  我們只需要第一次初始化的時候同步之後不要同步是最好的效率也高
  看來doublecheck即雙檢查會大大提高效率
  那就進入第四種單件模式 
   
      

四)第四種單件模式
  public sealed class Singleton
  {
   private static Singleton _instance;
   private static object _lockHelper = new object();
  
   private Singleton()
   { }
  
   public static Singleton Instance
   {
   get
   {
   if (_instance == null)
   {
   lock (_lockHelper)
   {
   if (_instance == null)
   {
   _instance = new Singleton();
   }
   }
   }
   return _instance;
   }
   }
  
   public void DoSomething()
   {
   ConsoleWriteLine(做些事情);
   }
  }
  記得看到過有人這麼問為什麼要雙檢查呢為什麼不改成如下代碼?
   public static Singleton Instance
   {
   get
   {
   if(_instance == null)
   {
   lock(_lockHelper)
   {
   _instance = new Singleton();
  }
  }
  return _instance
  }
  }
  那我們來分析一下
  線程執行到第行停下來
  線程執行到第因為線程在lock裡面被阻塞停下來
  線程繼續執行第初始化一個_instance設為obj退出lock區
  線程進入lock裡面也初始化一個_instance設為obj退出lock區
  現在又是雙件了所以為什麼要叫doublecheck雙檢查也是來源與此
  
  這個單件模式被用的最多但它就真的那麼完美無缺嗎? 不
  因為上面的代碼被編譯編譯器由於考慮時間和空間的問題會對代碼進行優化指令的順序可能也會被改變
  所以在多線程中可能還是會出狀況雖然這種概率很低但要是有解決方法為什麼不用呢?
  那就來看第五種單件模式
  
  五)第五種單件模式
  public sealed class Singleton
  {
   private static volatile Singleton _instance;
   private static object _lockHelper = new object();
  
   private Singleton()
   { }
  
   public static Singleton Instance
   {
   get
   {
   if (_instance == null)
   {
   lock (_lockHelper)
   {
   if (_instance == null)
   {
   _instance = new Singleton();
   }
   }
   }
   return _instance;
   }
   }
  
   public void DoSomething()
   {
   ConsoleWriteLine(做些事情);
   }
  }
  小菜並沒有做多少事只是把private static Singleton _instance;修改為private static volatile Singleton _instance;
  volatile關鍵字有這麼大魔力? 對
  編譯器保證對此_instance的讀寫操作都不會被優化
  
  接下來我們來講個集萬千寵愛與一生的單件模式
  六)第六個單件模式
  
  public sealed class Singleton
  {
   private Singleton()
   { }
  
   public static Singleton Instance
   {
   get
   {
   return Nestedinstance;
   }
   }
  
   public void DoSomething()
   {
   ConsoleWriteLine(做些事情);
   }
  
   //嵌套類
   private class Nested
   {
   internal static readonly Singleton instance;
  
   private Nested()
   { }
  
   static Nested()
   {
   instance = new Singleton();
   }
   }
  }
  return Nestedinstance保證了原子性
  第一次執行它時
  為Netsted的靜態數據成員Singleton instance分配內存空間存儲空間的值為null
  執行instance的靜態初始語句由於沒有所以跳過
  執行靜態構造函數執行 instance = new Singleton() 初始化instance
  返回instance對象
  
  很明顯用到的知識還是前面的對象構造順序可見有多重要
  
  到這裡單件模式的多種實現都介紹完了


From:http://tw.wingwit.com/Article/program/net/201311/12086.html
  • 上一篇文章:

  • 下一篇文章:
  • 推薦文章
    Copyright © 2005-2022 電腦知識網 Computer Knowledge   All rights reserved.