程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> c#單例形式(Singleton)的6種完成

c#單例形式(Singleton)的6種完成

編輯:C#入門知識

c#單例形式(Singleton)的6種完成。本站提示廣大學習愛好者:(c#單例形式(Singleton)的6種完成)文章只能為提供參考,不一定能成為您想要的結果。以下是c#單例形式(Singleton)的6種完成正文


1.1.1 摘要

 在我們日常的任務中常常需求在使用順序中堅持一個獨一的實例,如:IO處置,數據庫操作等,由於這些對象都要占用重要的零碎資源,所以我們必需限制這些實例的創立或一直運用一個公用的實例,這就是我們明天要引見的——單例形式(Singleton)。

 運用頻率

單件形式(Singleton):保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。

1.1.2 注釋

圖1單例形式(Singleton)構造圖

單例形式(Singleton)是幾個創立形式中最統一的一個,它的次要特點不是依據用戶順序調用生成一個新的實例,而是控制某個類型的實例獨一性,經過上圖我們知道它包括的角色只要一個,就是Singleton,它擁有一個公有結構函數,這確保用戶無法經過new直接實例它。除此之外,該形式中包括一個靜態公有成員變量instance與靜態私有辦法Instance()。Instance()辦法擔任檢驗並實例化自己,然後存儲在靜態成員變量中,以確保只要一個實例被創立。

圖2單例形式(Singleton)邏輯模型

接上去我們將引見6中不同的單例形式(Singleton)的完成方式。這些完成方式都有以下的共同點:

 1.有一個公有的無參結構函數,這可以避免其他類實例化它,而且單例類也不應該被承繼,假如單例類允許承繼那麼每個子類都可以創立實例,這就違犯了Singleton形式“獨一實例”的初衷。

2.單例類被定義為sealed,好像後面提到的該類不應該被承繼,所以為了保險起見可以把該類定義成不允許派生,但沒有要求一定要這樣定義。

3.一個靜態的變量用來保管單實例的援用。

4.一個私有的靜態辦法用來獲取單實例的援用,假如實例為null即創立一個。

版本一線程不平安

 /// <summary>
/// A simple singleton class implements.
/// </summary>
public sealed class Singleton
{
  private static Singleton _instance = null;

  /// <summary>
  /// Prevents a default instance of the 
  /// <see cref="Singleton"/> class from being created.
  /// </summary>
  private Singleton()
  {
  }

  /// <summary>
  /// Gets the instance.
  /// </summary>
  public static Singleton Instance
  {
    get { return _instance ?? (_instance = new Singleton()); }
  }
}

 以上的完成方式適用於單線程環境,由於在多線程的環境下有能夠失掉Singleton類的多個實例。假設同時有兩個線程去判別

(null == _singleton),並且失掉的後果為真,那麼兩個線程都會創立類Singleton的實例,這樣就違犯了Singleton形式“獨一實例”的初衷。

 版本二線程平安

 /// <summary>
/// A thread-safe singleton class.
/// </summary>
public sealed class Singleton
{
  private static Singleton _instance = null;
  private static readonly object SynObject = new object();

  Singleton()
  {
  }

  /// <summary>
  /// Gets the instance.
  /// </summary>
  public static Singleton Instance
  {
    get
    {
      // Syn operation.
      lock (SynObject)
      {
        return _instance ?? (_instance = new Singleton());
      }
    }
  }
}

以上方式的完成方式是線程平安的,首先我們創立了一個靜態只讀的進程輔佐對象,由於lock是確保當一個線程位於代碼的臨界區時,另一個線程不能進入臨界區(同步操作)。假如其他線程試圖進入鎖定的代碼,則它將不斷等候,直到該對象被釋放。從而確保在多線程下不會創立多個對象實例了。只是這種完成方式要停止同步操作,這將是影響零碎功能的瓶頸和添加了額定的開支。

 Double-Checked Locking

後面講到的線程平安的完成方式的問題是要停止同步操作,那麼我們能否可以降低經過操作的次數呢?其實我們只需在同步操作之前,添加判別該實例能否為null就可以降低經過操作的次數了,這樣是經典的Double-Checked Locking辦法。

 /// <summary>
/// Double-Checked Locking implements a thread-safe singleton class
/// </summary>
public sealed class Singleton
{
  private static Singleton _instance = null;
  // Creates an syn object.
  private static readonly object SynObject = new object();

  Singleton()
  {
  }

  public static Singleton Instance
  {
    get
    {
      // Double-Checked Locking
      if (null == _instance)
      {
        lock (SynObject)
        {
          if (null == _instance)
          {
            _instance = new Singleton();
          }
        }
      }
      return _instance;
    }
  }
}

 在引見第四種完成方式之前,首先讓我們看法什麼是,當字段被標志為beforefieldinit類型時,該字段初始化可以發作在任何時分任何字段被援用之前。這句話聽起了有點別扭,接上去讓我們經過詳細的例子引見。

 /// <summary>
/// Defines a test class.
/// </summary>
class Test
{
  public static string x = EchoAndReturn("In type initializer");

  public static string EchoAndReturn(string s)
  {
    Console.WriteLine(s);
    return s;
  }
}

下面我們定義了一個包括靜態字段和辦法的類Test,但要留意我們並沒有定義靜態的結構函數。

圖3 Test類的IL代碼

class Test
{
  public static string x = EchoAndReturn("In type initializer");

  // Defines a parameterless constructor.
  static Test()
  {
  }

  public static string EchoAndReturn(string s)
  {
    Console.WriteLine(s);
    return s;
  }
}

下面我們給Test類添加一個靜態的結構函數。

圖4 Test類的IL代碼

經過下面Test類的IL代碼的區別我們發現,當Test類包括靜態字段,而且沒有定義靜態的結構函數時,該類會被標志為beforefieldinit。

如今也許有人會問:“被標志為beforefieldinit和沒有標志的有什麼區別呢”?OK如今讓我們經過上面的詳細例子看一下它們的區別吧!

 class Test
{
  public static string x = EchoAndReturn("In type initializer");

  static Test()
  {
  }

  public static string EchoAndReturn(string s)
  {
    Console.WriteLine(s);
    return s;
  }
}

class Driver
{
  public static void Main()
  {
    Console.WriteLine("Starting Main");
    // Invoke a static method on Test
    Test.EchoAndReturn("Echo!");
    Console.WriteLine("After echo");
    Console.ReadLine();

    // The output result:
    // Starting Main
    // In type initializer
    // Echo!
    // After echo      
  }
}

我置信大家都可以失掉答案,假如在調用EchoAndReturn()辦法之前,需求完成靜態成員的初始化,所以最終的輸入後果如下:

圖5輸入後果

 接著我們在Main()辦法中添加string y = Test.x,如下:

public static void Main()
{
  Console.WriteLine("Starting Main");
  // Invoke a static method on Test
  Test.EchoAndReturn("Echo!");
  Console.WriteLine("After echo");

  //Reference a static field in Test
  string y = Test.x;
  //Use the value just to avoid compiler cleverness
  if (y != null)
  {
    Console.WriteLine("After field access");
  }
  Console.ReadKey();

  // The output result:
  // In type initializer
  // Starting Main
  // Echo!
  // After echo
  // After field access

}

圖6 輸入後果

經過下面的輸入後果,大家可以發現靜態字段的初始化跑到了靜態辦法調用之前,Wo難以想象啊!

最後我們在Test類中添加一個靜態結構函數如下:

 class Test
{
  public static string x = EchoAndReturn("In type initializer");

  static Test()
  {
  }

  public static string EchoAndReturn(string s)
  {
    Console.WriteLine(s);
    return s;
  }
}
 

圖7 輸入後果

實際上,type initializer應該發作在”Echo!”之後和”After echo”之前,但這裡卻呈現了不獨一的後果,只要當Test類包括靜態結構函數時,才干確保type initializer的初始化發作在”Echo!”之後和”After echo”之前。

所以說要確保type initializer發作在被字段援用時,我們應該給該類添加靜態結構函數。接上去讓我們引見單例形式的靜態方式。

 靜態初始化

 

public sealed class Singleton
{
  private static readonly Singleton _instance = new Singleton();

  // Explicit static constructor to tell C# compiler
  // not to mark type as beforefieldinit
  static Singleton()
  {
  }

  /// <summary>
  /// Prevents a default instance of the 
  /// <see cref="Singleton"/> class from being created.
  /// </summary>
  private Singleton()
  {
  }

  /// <summary>
  /// Gets the instance.
  /// </summary>
  public static Singleton Instance
  {
    get
    {
      return _instance;
    }
  }
}

以上方式完成比之前引見的方式都要復雜,但它的確是多線程環境下,C#完成的Singleton的一種方式。由於這種靜態初始化的方式是在自己的字段被援用時才會實例化。

 讓我們經過IL代碼來剖析靜態初始化。

圖8靜態初始化IL代碼

首先這裡沒有beforefieldinit的修飾符,由於我們添加了靜態結構函數當靜態字段被援用時才停止初始化,因而即使很多線程試圖援用_instance,也需求等靜態結構函數執行完並把靜態成員_instance實例化之後可以運用。

 延遲初始化

 /// <summary>
/// Delaies initialization.
/// </summary>
public sealed class Singleton
{
  private Singleton()
  {
  }

  /// <summary>
  /// Gets the instance.
  /// </summary>
  public static Singleton Instance { get { return Nested._instance; } }

  private class Nested
  {
    // Explicit static constructor to tell C# compiler
    // not to mark type as beforefieldinit
    static Nested()
    {
    }

    internal static readonly Singleton _instance = new Singleton();
  }
}

這裡我們把初始化任務放到Nested類中的一個靜態成員來完成,這樣就完成了延遲初始化。

 Lazy<T> type

 /// <summary>
/// .NET 4's Lazy<T> type
/// </summary>
public sealed class Singleton
{
  private static readonly Lazy<Singleton> lazy =
    new Lazy<Singleton>(() => new Singleton());

  public static Singleton Instance { get { return lazy.Value; } }

  private Singleton()
  {
  }
}

 這種方式的復雜和功能良好,而且還提供反省能否曾經創立實例的屬性IsValueCreated。

 詳細例子

如今讓我們運用單例形式(Singleton)完成負載均衡器,首先我們定義一個服務器類,它包括服務器名和IP地址如下:

 /// <summary>
/// Represents a server machine
/// </summary>
class Server
{
  // Gets or sets server name
  public string Name { get; set; }

  // Gets or sets server IP address
  public string IP { get; set; }
}

由於負載均衡器只提供一個對象實例供服務器運用,所以我們運用單例形式(Singleton)完成該負載均衡器。

 /// <summary>
/// The 'Singleton' class
/// </summary>
sealed class LoadBalancer
{
  private static readonly LoadBalancer _instance =
    new LoadBalancer();

  // Type-safe generic list of servers
  private List<Server> _servers;
  private Random _random = new Random();

  static LoadBalancer()
  {
  }

  // Note: constructor is 'private'
  private LoadBalancer()
  {
    // Load list of available servers
    _servers = new List<Server> 
      { 
       new Server{ Name = "ServerI", IP = "192.168.0.108" },
       new Server{ Name = "ServerII", IP = "192.168.0.109" },
       new Server{ Name = "ServerIII", IP = "192.168.0.110" },
       new Server{ Name = "ServerIV", IP = "192.168.0.111" },
       new Server{ Name = "ServerV", IP = "192.168.0.112" },
      };
  }

  /// <summary>
  /// Gets the instance through static initialization.
  /// </summary>
  public static LoadBalancer Instance
  {
    get { return _instance; }
  }


  // Simple, but effective load balancer
  public Server NextServer
  {
    get
    {
      int r = _random.Next(_servers.Count);
      return _servers[r];
    }
  }
}

 下面負載均衡器類LoadBalancer我們運用靜態初始化方式完成單例形式(Singleton)。

 static void Main()
{
  LoadBalancer b1 = LoadBalancer.Instance;
  b1.GetHashCode();
  LoadBalancer b2 = LoadBalancer.Instance;
  LoadBalancer b3 = LoadBalancer.Instance;
  LoadBalancer b4 = LoadBalancer.Instance;

  // Confirm these are the same instance
  if (b1 == b2 && b2 == b3 && b3 == b4)
  {
    Console.WriteLine("Same instance\n");
  }

  // Next, load balance 15 requests for a server
  LoadBalancer balancer = LoadBalancer.Instance;
  for (int i = 0; i < 15; i++)
  {
    string serverName = balancer.NextServer.Name;
    Console.WriteLine("Dispatch request to: " + serverName);
  }

  Console.ReadKey();
}

圖9 LoadBalancer輸入後果

 1.1.3 總結

單例形式的優點:

單例形式(Singleton)會控制其實例對象的數量,從而確保訪問對象的獨一性。

1.實例控制:單例形式避免其它對象對自己的實例化,確保一切的對象都訪問一個實例。

2.伸縮性:由於由類自己來控制實例化進程,類就在改動實例化進程上有相應的伸縮性。

 單例形式的缺陷:

1.零碎開支。雖然這個零碎開支看起來很小,但是每次援用這個類實例的時分都要停止實例能否存在的反省。這個問題可以經過靜態實例來處理。

2.開發混雜。當運用一個單例形式的對象的時分(特別是定義在類庫中的),開發人員必需要記住不能運用new關鍵字來實例化對象。由於開發者看不到在類庫中的源代碼,所以當他們發現不能實例化一個類的時分會很詫異。

3.對象生命周期。單例形式沒有提出對象的銷毀。在提供內存管理的開發言語(比方,基於.NetFramework的言語)中,只要單例形式對象自己才干將對象實例銷毀,由於只要它擁有對實例的援用。在各種開發言語中,比方C++,其它類可以銷毀對象實例,但是這麼做將招致單例類外部的指針指向不明。 

單例適用性

運用Singleton形式有一個必要條件:在一個零碎要求一個類只要一個實例時才該當運用單例形式。反之,假如一個類可以有幾個實例共存,就不要運用單例形式。

不要運用單例形式存取全局變量。這違犯了單例形式的意圖,最好放到對應類的靜態成員中。

不要將數據庫銜接做成單例,由於一個零碎能夠會與數據庫有多個銜接,並且在有銜接池的狀況下,該當盡能夠及時釋放銜接。Singleton形式由於運用靜態成員存儲類實例,所以能夠會形成資源無法及時釋放,帶來問題。

以上就是本文的全部內容,希望對大家的學習有所協助,也希望大家多多支持。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved