跳轉到內容

.NET 開發基金會/泛型

來自華夏公益教科書,開放書籍,開放世界


系統型別和集合:泛型


附錄:泛型

[編輯 | 編輯原始碼]

為了理解泛型,我們首先應該看看為什麼我們要使用它們。這可以用一個簡單的例子來解釋。假設我們想要建立一個客戶集合,這是我們經常做的事情。我們採用一個簡單的 Client 類,它具有姓名和帳號。通常使用 ArrayList 來儲存記憶體中的多個客戶,如下所示

  /// <summary>
  /// Representation of a client
  /// </summary>
  public class Client
  {
      private string _name;
      private string _accountNumber;
       
      /// <summary>
      /// Gets or sets the account number.
      /// </summary>
      /// <value>The account number.</value>
      public string AccountNumber
      {
          get { return _accountNumber; }
          set { _accountNumber = value; }
      }
       
      /// <summary>
      /// Gets or sets the name.
      /// </summary>
      /// <value>The name.</value>
      public string Name
      {
          get { return _name; }
          set { _name = value; }
      }
       
      /// <summary>
      /// Initializes a new instance of the <see cref="T:Client"/> class.
      /// </summary>
      /// <param name="name">The name.</param>
      /// <param name="accountNumber">The account number.</param>
      public Client(string name, string accountNumber)
      {
          _name = name;
          _accountNumber = accountNumber;
      }
       
      /// <summary>
      /// The Main entry point of the console application
      /// </summary>
      /// <param name="args">The command line arguments</param>
      static void Main(string[] args)
      {
          ArrayList clients = new ArrayList();
          clients.Add(new Client("Marco", "332-3355"));
          clients.Add(new Client("Martinus", "453-5662"));
          foreach (Client client in clients)
          {
              Console.WriteLine("The account {0} belongs to {1}", client.AccountNumber, client.Name);
          }
          Console.ReadLine();
      }
  }

這對於大多數讀者來說可能很熟悉,但是透過使用 ArrayList,我們不是型別安全的,在我看來,這是不好的。此外,當我們想要對物件做一些很酷的事情時,我們需要強制轉換(和取消強制轉換)物件。對於值型別,還有一個問題,我們不斷地對列表中的物件進行裝箱和拆箱。

儲存客戶的更好方法是使用型別化集合,這將解決型別安全問題,我們不必在每次使用檢索到的物件時都強制轉換它。一個很好的方法是從 CollectionBase 類繼承一個物件,看起來像這樣

  /// <summary>
  /// Representation of a client
  /// </summary>
  public class Client
  {
      private string _name;
      private string _accountNumber;
       
      /// <summary>
      /// Gets or sets the account number.
      /// </summary>
      /// <value>The account number.</value>
      public string AccountNumber
      {
          get { return _accountNumber; }
          set { _accountNumber = value; }
      }
       
      /// <summary>
      /// Gets or sets the name.
      /// </summary>
      /// <value>The name.</value>
      public string Name
      {
          get { return _name; }
          set { _name = value; }
      }
       
      /// <summary>
      /// Initializes a new instance of the <see cref="T:Client"/> class.
      /// </summary>
      /// <param name="name">The name.</param>
      /// <param name="accountNumber">The account number.</param>
      public Client(string name, string accountNumber)
      {
          _name = name;
          _accountNumber = accountNumber;
      }
       
      /// <summary>
      /// The Main entry point of the console application
      /// </summary>
      /// <param name="args">The command line arguments</param>
      static void Main(string[] args)
      {
          ClientList clients = new ClientList();
          clients.Add(new Client("Marco", "332-3355"));
          clients.Add(new Client("Martinus", "453-5662"));
          foreach (Client client in clients)
          {
              Console.WriteLine("The account {0} belongs to {1}", client.AccountNumber, client.Name);
          }
          Console.ReadLine();
      }
  }
           
  /// <summary>
  /// A list of clients
  /// </summary>
  public class ClientList : CollectionBase
  {
      /// <summary>
      /// Adds the specified client to the list.
      /// </summary>
      /// <param name="client">The client.</param>
      /// <returns></returns>
      public int Add(Client client)
      {
          return List.Add(client);
      }
       
      /// <summary>
      /// Gets or sets the <see cref="T:Client"/> at the specified index.
      /// </summary>
      /// <value></value>
      public Client this[int index]
      {
          get
          {
              return (Client)List[index];
          }
          set
          {
              List[index] = value;
          }
      }
  }


這看起來比我們在第一個例子中使用 ArrayList 時要好得多,並且通常是一種很好的方法。但是如果您的應用程式不斷增長,並且我們得到了更多想要儲存在集合中的型別,例如 Account 或 bank。在 1.1 框架中,我們必須為我們使用的每種型別的物件建立一個新的集合類,或者回退到醜陋的 ArrayList 方法。但是,隨著新的 2.0 框架的釋出,MS 添加了泛型。這使得建立使用型別引數的類和方法成為可能。這允許開發人員建立在類定義和程式碼中例項化之前推遲某些型別規範的類和方法。透過使用泛型型別引數,開發人員可以編寫其他人可以使用而不會冒 ArrayList 等非型別化類帶來的風險的類,並且與建立型別化集合相比,它減少了開發人員必須完成的工作。所以讓我們看看當我們使用框架中的 Generic List<T> 類時程式碼的樣子。

  /// <summary>
  /// Representation of a client
  /// </summary>
  public class Client
  {
      private string _name;
      private string _accountNumber;
       
      /// <summary>
      /// Gets or sets the account number.
      /// </summary>
      /// <value>The account number.</value>
      public string AccountNumber
      {
          get { return _accountNumber; }
          set { _accountNumber = value; }
      }
       
      /// <summary>
      /// Gets or sets the name.
      /// </summary>
      /// <value>The name.</value>
      public string Name
      {
          get { return _name; }
          set { _name = value; }
      }
       
      /// <summary>
      /// Initializes a new instance of the <see cref="T:Client"/> class.
      /// </summary>
      /// <param name="name">The name.</param>
      /// <param name="accountNumber">The account number.</param>
      public Client(string name, string accountNumber)
      {
          _name = name;
          _accountNumber = accountNumber;
      }
       
      /// <summary>
      /// The Main entry point of the console application
      /// </summary>
      /// <param name="args">The command line arguments</param>
      static void Main(string[] args)
      {
          List<Client> clients = new List<Client>();
          clients.Add(new Client("Marco", "332-3355"));
          clients.Add(new Client("Martinus", "453-5662"));
          foreach (Client client in clients)
          {
              Console.WriteLine("The account {0} belongs to {1}", client.AccountNumber, client.Name);
          }
          Console.ReadLine();
      }
  }

由於泛型,我們建立了一個型別安全的集合,就像我們通常例項化 ArrayList 一樣容易,而無需編寫我們自己的型別化集合。現在,我們已經簡要地瞭解瞭如何使用泛型來減少我們需要的程式碼量,同時仍然使用型別化集合,讓我們看看如何建立我們自己的自定義泛型類。為了說明這一點,我建立了一個示例類,它從 DictionaryBase 類繼承。此字典類的實現將接受 GUID 作為鍵,並具有型別引數作為值型別。如果您像我一樣,您將使用 GUID 來標識資料庫中的記錄,因此在型別化集合中使用它作為鍵是我很喜歡做的事情。所以現在我得到了一個很酷的字典,我可以使用我從資料庫中檢索資料時建立的所有物件,我將提供程式碼

  /// <summary>
  /// Representation of a client
  /// </summary>
  public class Client
  {
      private string _name;
      private string _accountNumber;
       
      /// <summary>
      /// Gets or sets the account number.
      /// </summary>
      /// <value>The account number.</value>
      public string AccountNumber
      {
          get { return _accountNumber; }
          set { _accountNumber = value; }
      }
       
      /// <summary>
      /// Gets or sets the name.
      /// </summary>
      /// <value>The name.</value>
      public string Name
      {
          get { return _name; }
          set { _name = value; }
      }
       
      /// <summary>
      /// Initializes a new instance of the <see cref="T:Client"/> class.
      /// </summary>
      /// <param name="name">The name.</param>
      /// <param name="accountNumber">The account number.</param>
      public Client(string name, string accountNumber)
      {
          _name = name;
          _accountNumber = accountNumber;
      }
       
      /// <summary>
      /// The Main entry point of the console application
      /// </summary>
      /// <param name="args">The command line arguments</param>
      static void Main(string[] args)
      {
          GuidDictionary<Client> clients = new GuidDictionary<Client>();
          Guid clientID1 = Guid.NewGuid();
          Guid clientID2 = Guid.NewGuid();
          clients.Add(clientID1, new Client("Marco", "332-3355"));
          clients.Add(clientID2, new Client("Martinus", "453-5662"));
          Console.WriteLine("The account {0} belongs to {1}", clients[clientID1].AccountNumber, clients[clientID1].Name);
          Console.WriteLine("The account {0} belongs to {1}", clients[clientID2].AccountNumber, clients[clientID2].Name);
          Console.ReadLine();
      }
  }
       
  public class GuidDictionary<T> : DictionaryBase
  {
      public void Add(Guid id, T item)
      {
          Dictionary.Add(id, item);
      }
       
      public T this[Guid id]
      {
          get
          {
              return (T)Dictionary[id];
          }
          set
          {
              Dictionary[id] = value;
          }
      }
  }

好吧,它可能沒有您期望的那麼棒,標準字典的許多方法甚至沒有實現,但是嘿,我必須留下一些有趣的事情讓您這個讀者去做。

那麼我們在上面的程式碼中到底做了什麼?我們建立了一個由 Guid 索引並使用型別引數來限制我們的 add 方法和索引器的字典。現在,當我們建立該類的新例項並指定型別引數時,我們得到了一個型別安全的字典,該字典可以用於透過給定的 Guid 儲存和檢索物件的型別。

宣告中的 T 只是一個名稱,我們可以像使用 VeryLongNameForTheTypeParameter 而不是 T 一樣輕鬆地使用它。好的,我們已經看到了如何使用型別引數來建立一個泛型類。但在我們繼續下一部分之前,讓我們看看 System.Collections.Generic.Dictionary<>。此類必須透過提供名為 TKey 和 TValue 的兩個型別引數來例項化。這表明我們可以在定義中使用多個型別引數,並且還表明構建自己的字典類是浪費時間,我可以像使用 Dictionary<Guid, Client> 這樣輕鬆地使用泛型字典,就可以了。

[編輯 | 編輯原始碼]
華夏公益教科書