程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> 關於C# >> C#泛型秘訣(1)

C#泛型秘訣(1)

編輯:關於C#

4.0 介紹

泛型,一個期待已久的功能,隨著C# 2.0版本編譯器的到來最終出現。泛型是一個非常有用的功能,它使得您的代碼變得精簡而富有效率。這些將在秘訣4.1進行詳細講述。泛型的到來使得您可以編寫更為強大的應用程序,但這需要正確地使用它。如果您考慮把ArrayList,Queue,Stack和Hashtable對象轉變為使用相應的泛型版本,可以閱讀秘訣4.4,4.5和4.10。當您閱讀過後,會發現這種轉變不一定簡單,甚至有可能會不再打算進行轉變。

本章的另外一些秘訣涉及到.NET Framework 2.0所包含的其他泛型類,如秘訣4.6。其他秘訣講述一些泛型類的操作,如秘訣4.2,4.8和4.13。

4.1決定在何時何地使用泛型

問題

您希望在一個新工程內使用泛型,或者想把已有項目中的非泛型類轉換為等價的泛型版本。但您並非了解為何要這樣做,也不知道哪個非泛型類應該被轉換為泛型類。

解決方案

決定在何時何地使用泛型,您需要考慮以下幾件事件:

l 您所使用的類型是否包含或操作未指定的數據類型(如集合類型)?如果是這樣,如果是這樣,創建泛型類型將能提供更多的好處。如果您的類型只操作單一的指定類型,那麼就沒有必要去創建一個泛型類。

l 如果您的類型將操作值類型,那麼就會產生裝箱和拆箱操作,就應該考慮使用泛型來防止裝箱和拆箱操作。

l 泛型的強類型檢查有助於快速查找錯誤(也就是編譯期而非運行期),從而縮短bug修復周期。

l 在編寫多個類操作多個數據類型時是否遭遇到“代碼膨脹”問題(如一個ArrayList只存儲StreamReaders而另一個存儲StreamWriters)?其實編寫一次代碼並讓它工作於多個數據類型非常簡單。

l 泛型使得代碼更為清晰。通過消除代碼膨脹並進行強制檢查,您的代碼將變得更易於閱讀和理解。

討論

很多時候,使用泛型類型將使您受益。泛型將使得代碼重用更有效率,具有更快的執行速度,進行強制類型檢查,獲得更易讀的代碼。

閱讀參考

MSDN文檔中的“Generics Overview”和“Benefits of Generics”主題。

4.2 理解泛型類型

問題

您需要理解泛型類型在.NET中是如何工作的,它跟一般的.NET類型有什麼不同。

解決方案

幾個小實驗就可以演示一般類型和泛型類型之間的區別。例4-1中的StandardClass類就是一個般類型。

例4-1 StandardClass:一般的.NET類型

public class StandardClass
{ 
  static int _count = 0; //StandardClass類的對象的表態計數器
  int _maxItemCount; //項數目的最大值
  object[] _items; //保存項的數組
  int _currentItem = 0; //當前項數目
  public StandardClass(int items) //構造函數
  {
    _count++; //對象數目加
    _maxItemCount = items;
    _items = new object[_maxItemCount]; //數組初始化
  }
  //用於添加項,為了適用於任何類型,使用了object類型
  public int AddItem(object item)
  {
    if (_currentItem < _maxItemCount)
    {
      _items[_currentItem] = item;
      return _currentItem++; //返回添加的項的索引
    }
    else
      throw new Exception("Item queue is full ");
  }
  //用於從類中取得指定項
  public object GetItem(int index)
  {
    Debug.Assert(index < _maxItemCount); //設置斷言
    if (index >= _maxItemCount)
      throw new ArgumentOutOfRangeException("index");
    return _items[index]; //返回指定項
  }
  public int ItemCount //屬性,指示當前項數目
  {
    get { return _currentItem; }
  }
  public override string ToString( )
  {  //重載ToString方法,用於介紹類的情況
    return "There are " + _count.ToString( ) +
      " instances of " + this.GetType( ).ToString( ) +
      " which contains " + _currentItem + " items of type " +
      _items.GetType( ).ToString( ) + "";
  }
}

StandardClass類有一個整型靜態成員變量_count,用於在實例構造器中計數。重載的ToString()方法打印在這個應用程序域中StandardClass類實例的數目。StandardClass類還包括一個object數組(_item),它的長度由構造方法中的傳遞的參數來決定。它實現了添加和獲得項的方法(AddItem,GetItem),還有一個只讀屬性來獲取數組中的項的數目(ItemCount)。

GenericClass<T>類是一個泛型類型,同樣有靜態成員變量_count,實例構造器中對實例數目進行計算,重載的ToString()方法告訴您有多少GenericClass<T>類的實例存在。GenericClass<T>也有一個_itmes數組和StandardClass類中的相應方法,請參考例4-2。

Example4-2 GenericClass<T>:泛型類

public class GenericClass<T>
{
  static int _count = 0;
  int _maxItemCount;
  T[] _items;
  int _currentItem = 0;
  public GenericClass(int items)
  {
    _count++;
    _ _maxItemCount = items;
    _items = new T[_maxItemCount];
  }
  public int AddItem(T item)
  {
    if (_currentItem < _maxItemCount)
    {
      _items[_currentItem] = item;
      return _currentItem++;
    }
    else
      throw new Exception("Item queue is full ");
  }
  public T GetItem(int index)
  {
    Debug.Assert(index < _maxItemCount);
    if (index >= _maxItemCount)
      throw new ArgumentOutOfRangeException("index");
    return _items[index];
  }
  public int ItemCount
  {
    get { return _currentItem; }
  }
  public override string ToString()
  {
    return "There are " + _count.ToString() +
      " instances of " + this.GetType().ToString() +
      " which contains " + _currentItem + " items of type " +
      _items.GetType().ToString() + "";
  }
}

從GenericClass<T>中的少許不同點開始,看看_items數組的聲明。它聲明為:

T[] _items;

而不是

object[] _items;

_items數組使用泛型類(<T>)做為類型參數以決定在_itmes數組中接收哪種類型的項。StandarClass在_itmes數組中使用Objcec以使得所有類型都可以做為項存儲在數組中(因為所有類型都繼承自object)。而GenericClass<T>通過使用類型參數指示允許使用的對象類型來提供類型安全。

下一個不同在於AddItem和GetItem方法的聲明。AddItem現在使用一個類型T做為參數,而在StandardClass中使用object類型做為參數。GetItem現在的返回值類型T,StandardClass返回值為object類型。這個改變允許GenericClass<T>中的方法在數組中存儲和獲得具體的類型而非StandardClass中的允許存儲所有的object類型。

public int AddItem(T item)
  {
    if (_currentItem < _maxItemCount)
    {
      _items[_currentItem] = item;
      return _currentItem++;
    }
    else
      throw new Exception("Item queue is full ");
  }
  public T GetItem(int index)
  {
    Debug.Assert(index < _maxItemCount);
    if (index >= _maxItemCount)
      throw new ArgumentOutOfRangeException("index");
    return _items[index];
  }

這樣做的優勢在於,首先通過GenericClass<T>為數組中的項提供了類型安全。在StandardClass中可能會這樣寫代碼:

// 一般類
  StandardClass C = new StandardClass(5);
  Console.WriteLine(C);
  string s1 = "s1";
  string s2 = "s2";
  string s3 = "s3";
  int i1 = 1;
  // 在一般類中以object的形式添加項
  C.AddItem(s1);
  C.AddItem(s2);
  C.AddItem(s3);

// 在字符串數組中添加一個整數,也被允許

C.AddItem(i1);

但在GenericClass<T>中做同樣的事情將導致編譯錯誤:

  // 泛型類
  GenericClass<string> gC = new GenericClass<string>(5);
  Console.WriteLine(gC);
  string s1 = "s1";
  string s2 = "s2";
  string s3 = "s3";
  int i1 = 1;
  // 把字符串添加進泛型類.
  gC.AddItem(s1);
  gC.AddItem(s2);
  gC.AddItem(s3);
  // 嘗試在字符串實例中添加整數,將被編譯器拒絕
  // error CS1503: Argument '1': cannot convert from 'int' to 'string'
//GC.AddItem(i1);

編譯器防止它成為運行時源碼的bug,這是一件非常美妙的事情。

雖然並非顯而易見,但在StandardClass中把整數添加進object數組會導致裝箱操作,這一點可以StandardClass調用GetItem方法時的IL代碼:

Il _0170: ldloc.2
  Il _0171: ldloc.s i1 
  Il _0173: box [mscorlib]System.Int32
  Il _0178: callvirt instance int32 CSharpRecipes.Generics/StandardClass::AddItem(object)

這個裝箱操作把做為值類型的整數轉換為引用類型(object),從而可以在數組中存儲。這導致了在object數組中存儲值類型時需要增加額外的工作。

當您在運行StandardClass並從類中返回一個項時,還會產生一個問題,來看看StandardClass.GetItem如何返回一個項:

  // 存儲返回的字符串.
  string sHolder;
  // 發生錯誤CS0266:
  // Cannot implicitly convert type 'object' to 'string'…
  sHolder = (string)C.GetItem(1);

因為StandardClass.GetItem返回的是object類型,而您希望通過索引1獲得一個字符串類型,所以需要把它轉換為字符串類型。然而它有可能並非字符串-----只能確定它是一個object-----但為了賦值正確,您不得不把它轉換為更明確的類型。字符串比較特殊,所有對象都可以自行提供一個字符串描述,但當數組接收一個double類型並把它賦給一個布爾類型就會出問題。

這兩個問題在GenericClass<T>中被全部解決。無需再進行拆箱,因為GetItem所返回的是一個具體類型,編譯器會檢查返回值以強近它執行。

   string sHolder;
  int iHolder;
  // 不需要再進行轉換
  sHolder = gC.GetItem(1);
  // 嘗試把字符串變為整數.將出現
  // 錯誤CS0029: Cannot implicitly convert type 'string' to 'int'
//iHolder = gC.GetItem(1);

為了了解兩種類型的其他不同點,分別給出它們的示例代碼:

// 一般類
  StandardClass A = new StandardClass();
  Console.WriteLine(A);
  StandardClass B = new StandardClass();
  Console.WriteLine(B);
  StandardClass C = new StandardClass();
  Console.WriteLine(C);
  // 泛型類
  GenericClass<bool > gA = new GenericClass<bool >();
  Console.WriteLine(gA);
  GenericClass<int> gB = new GenericClass<int>();
  Console.WriteLine(gB);
  GenericClass<string> gC = new GenericClass<string>();
  Console.WriteLine(gC);
  GenericClass<string> gD = new GenericClass<string>();
  Console.WriteLine(gD);

上述代碼輸出結果如下:

There are 1 instances of CSharpRecipes.Generics+StandardClass which contains 0 items of type System.Object[]...
There are 2 instances of CSharpRecipes.Generics+StandardClass which contains 0 items of type System.Object[]...
There are 3 instances of CSharpRecipes.Generics+StandardClass which contains 0 items of type System.Object[]...
There are 1 instances of CSharpRecipes.Generics+GenericClass`1[System.Boolean] which contains 0 items of type System.Boolean[]...
There are 1 instances of CSharpRecipes.Generics+GenericClass`1[System.Int32] which contains 0 items of type System.Int32[]...
There are 1 instances of CSharpRecipes.Generics+GenericClass`1[System.String] which contains 0 items of type System.String[]...
There are 2 instances of CSharpRecipes.Generics+GenericClass`1[System.String] which contains 0 items of type System.String[]...

討論

泛型中的類型參數允許您在不知道使用何種類型的情況下提供類型安全的代碼。在很多場合下,您希望類型具有某些指定的特征,這可以通過使用類型約束(秘訣4.12)來實現。方法在類本身不是泛型的情況下也可以擁有泛型類型的參數。秘訣4.9為此演示了一個例子。

注意當StandardClass擁有三個實例,GenericClass有一個聲明為<bool >類型的實例,一個聲明為<int>類型的實例,兩個聲明為<string>類型的實例。這意味著所有非泛型類都創建同一.NET類型對象,而所有泛型類都為指定類型實例創建自己的.NET類型對象。

示例代碼中的StandardClass類有三個實例,因為CLR中只維護一個StandardClass類型。而在泛型中,每種類型都被相應的類型模板所維護,當創建一個類型實例時,類型實參被傳入。說得更清楚一些就是為GenericClass<bool >產生一個類型,為GenericClass<int>產生一個類型,為GenericClass<string>產生第三個類型。

內部靜態成員_count可以幫助說明這一點,一個類的靜態成員實際上是跟CLR中的類型相連的。CLR對於給定的類型只會創建一次,並維護它一直到應用程序域卸載。這也是為什麼在調用ToString方法時,輸出顯示有StandardClass的三個實例,而GenericClass<T>類型有1和2個實例。

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