跳转到内容

.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> 这样轻松地使用泛型字典,就可以了。

[编辑 | 编辑源代码]
华夏公益教科书