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

幫助.NET泛型的理解

編輯:C#基礎知識
【基礎】泛型的簡單理解

一、前言

最近工作不是很忙,抽出時間來看看C#中基礎的東西,也算是“溫故而知新”了,於是就看到了泛型這塊兒,看了園子裡其他園友的文章,講的都很到位。這篇文章本著簡單、容易理解為前提,記錄下我自己對泛型的認識,並記錄下來,方便以後查看。

二、泛型是什麼?

泛型是一種開放式類型,它的出現保證了我們可以創建類型安全的集合。

三、泛型的應用場景

<1>當一種邏輯適用於多種數據類型時,可以采用泛型,簡化代碼,提高代碼的復用性。

<2>避免不必要的裝箱(Boxing)和拆箱(Unboxing)操作。

<3>避免不必要的類型強制轉換。

下面通過一個自定義的棧,來對上面的應用場景進行解釋。

首先定義一個棧,該棧有一個含參構造函數,有三個方法,元素的入棧Push、元素的出棧Pop、元素的輸出Out。代碼如下:

 /// <summary>
 /// 自定義棧
 /// </summary>
 /// <typeparam name="?"></typeparam>
 class Stack
 {
     private int[] arr;
     private int length = 0;
     private int topElement;
 
     /// <summary>
     /// 設置或獲取棧頂元素
     /// </summary>
     public int TopElement
     {
         get
         {
             this.topElement= arr[length-1];
             return this.topElement;
         }
         set
         {
             this.topElement = value;
         }
     }
 
     /// <summary>
     /// 棧中已存在元素數量
     /// </summary>
     public int Length
     {
         get { return this.length; }
     }
 
     /// <summary>
     /// 構造函數
     /// </summary>
     /// <param name="m"></param>
     public Stack(int m)
     {
         this.arr = new int[m];
     }
 
     /// <summary>
     /// 將棧頂元素出棧
     /// </summary>
     /// <returns></returns>
     public int Pop()
     {
         if (this.length <= 0)
             return -1;
         else
         {
             int popElement = this.topElement;
             arr[this.length-1] = 0;
             this.length = this.length - 1;
             return popElement;
         }
     }
 
     /// <summary>
     /// 將元素入棧
     /// </summary>
     /// <param name="item">目標元素</param>
     public void Push(int item)
     {
         if (length >= this.arr.Length) return;
         this.arr[length] = item;
         this.topElement = this.arr[length];
         this.length = this.length + 1;
     }
 
     /// <summary>
     /// 輸出棧內所有元素
     /// </summary>
     /// <param name="stack">目標棧</param>
     public void Out(Stack stack)
     {
         if (stack.arr.Length <= 0) return;
         foreach (int item in arr)
         {
             Console.WriteLine(item);            
         }
         Console.ReadLine();
     }
 }

以上代碼可以正常運行,實現了一個棧的基本功能,但是我想把這個邏輯放在string類型上也通用,怎麼辦?有人說那好辦,把int替換成string就好咯,代碼如下:

 /// <summary>
 /// 自定義棧
 /// </summary>
 /// <typeparam name="?"></typeparam>
 class StringStack
 {
     private string[] arr;
     private int length = 0;
     private string topElement;
 
     /// <summary>
     /// 設置或獲取棧頂元素
     /// </summary>
     public string TopElement
     {
         get
         {
             this.topElement = arr[length - 1];
             return this.topElement;
         }
         set
         {
             this.topElement = value;
         }
     }
 
     /// <summary>
     /// 棧中已存在元素數量
     /// </summary>
     public int Length
     {
         get { return this.length; }
     }
 
     /// <summary>
     /// 構造函數
     /// </summary>
     /// <param name="m"></param>
     public StringStack(int m)
     {
         this.arr = new string[m];
     }
 
     /// <summary>
     /// 將棧頂元素出棧
     /// </summary>
     /// <returns></returns>
     public string Pop()
     {
         if (this.length <= 0)
             return "Empty";
         else
         {
             string popElement = this.topElement;
             arr[this.length - 1] = string.Empty;
             this.length = this.length - 1;
             return popElement;
         }
     }
 
     /// <summary>
     /// 將元素入棧
     /// </summary>
     /// <param name="item">目標元素</param>
     public void Push(string item)
     {
         if (length >= this.arr.Length) return;
         this.arr[length] = item;
         this.topElement = this.arr[length];
         this.length = this.length + 1;
     }
 
     /// <summary>
     /// 輸出棧內所有元素
     /// </summary>
     /// <param name="stack">目標棧</param>
     public void Out(StringStack stack)
     {
         if (stack.arr.Length <= 0) return;
         foreach (string item in arr)
         {
             Console.WriteLine(item);
         }
         Console.ReadLine();
     }
 }

經過測試發現,上面的棧也可以正常工作了,但是問題又來了,我想把這個棧的邏輯再應用到long類型上怎麼辦?聰明的人不會在同一個問題上絆倒三次,既然有那麼多類型需要用到這個棧的邏輯,那我索性就給他來個Object,這下總可以了吧~於是Object類型的棧橫空出世,代碼如下:

 /// <summary>
 /// 自定義棧
 /// </summary>
 /// <typeparam name="?"></typeparam>
 class ObjectStack
 {
     private object[] arr;
     private int length = 0;
     private object topElement;
 
     /// <summary>
     /// 設置或獲取棧頂元素
     /// </summary>
     public object TopElement
     {
         get
         {
             this.topElement = arr[length - 1];
             return this.topElement;
         }
         set
         {
             this.topElement = value;
         }
     }
 
     /// <summary>
     /// 棧中已存在元素數量
     /// </summary>
     public int Length
     {
         get { return this.length; }
     }
 
     /// <summary>
     /// 構造函數
     /// </summary>
     /// <param name="m"></param>
     public ObjectStack(int m)
     {
         this.arr = new string[m];
     }
 
     /// <summary>
     /// 將棧頂元素出棧
     /// </summary>
     /// <returns></returns>
     public object Pop()
     {
         if (this.length <= 0)
             return null;
         else
         {
             object popElement = this.topElement;
             arr[this.length - 1] = null;
             this.length = this.length - 1;
             return popElement;
         }
     }
 
     /// <summary>
     /// 將元素入棧
     /// </summary>
     /// <param name="item">目標元素</param>
     public void Push(object item)
     {
         if (length >= this.arr.Length) return;
         this.arr[length] = item;
         this.topElement = this.arr[length];
         this.length = this.length + 1;
     }
 
     /// <summary>
     /// 輸出棧內所有元素
     /// </summary>
     /// <param name="stack">目標棧</param>
     public void Out(ObjectStack stack)
     {
         if (stack.arr.Length <= 0) return;
         foreach (object item in arr)
         {
             Console.WriteLine(item);
         }
         Console.ReadLine();
     }
 }

經過重構之後,這個棧邏輯可以適用於任何類型,這是毫無疑問的,但是還有一個問題,來看調用的代碼:

 static void Main(string[] args)
 {
     ObjectStack ostd = new ObjectStack(3);
     ostd.Push(true);
     ostd.Push("http://www.cnblogs.com/xhb-bky-blog/");
     ostd.Push(12);
 
     int a = (int)ostd.Pop();
     string b = ostd.Pop().ToString();
     bool c = (bool)ostd.Pop();
     Console.ReadLine();
 }
         

在調用代碼中,我們居然看到了裝箱和拆箱操作,而裝箱操作和拆箱操作是要額外耗費cpu和內存資源的。再來看下面一段代碼:

         static void Main(string[] args)
         {            
             ObjectStack ostd = new ObjectStack(3);
             Good good1 = new Good("pencil", 5);
             Good good2 = new Good("eraser", 2);
             Good good3 = new Good("pencilbox", 15);
             
             ostd.Push(good1);
             ostd.Push(good2);
             ostd.Push(good3);
 
             Good mygood = (Good)ostd.Pop();//注意
             Console.ReadLine();
         }

上面的代碼中發生了類型強制轉換,這表明該段代碼是不安全的,雖然可以通過編譯,但是如果類型對應不上,就會把錯誤推遲到運行期。為了解決上面的三個問題,泛型應運而生,來看一下泛型是如何解決這些問題的,看代碼:

 /// <summary>
 /// 自定義棧
 /// </summary>
 /// <typeparam name="T"></typeparam>
 class Stack<T> where T:IComparable
 {
     private T[] arr;
     private int length = 0;
     private T topElement;
 
     /// <summary>
     /// 設置或獲取棧頂元素
     /// </summary>
     public T TopElement
     {
         get
         {
             this.topElement = arr[length - 1];
             return this.topElement;
         }
         set
         {
             this.topElement = value;
         }
     }
 
     /// <summary>
     /// 棧中已存在元素數量
     /// </summary>
     public int Length
     {
         get { return this.length; }
     }
 
     /// <summary>
     /// 構造函數
     /// </summary>
     /// <param name="m"></param>
     public Stack(int m)
     {
         this.arr = new T[m];
     }
 
     /// <summary>
     /// 將棧頂元素出棧
     /// </summary>
     /// <returns></returns>
     public T Pop()
     {
         //T t = new T();//必須聲明new
         if (this.length <= 0)
             return default(T);
         else
         {
             T popElement = this.topElement;
             arr[this.length - 1] = default(T);
             this.length = this.length - 1;
             return popElement;
         }
     }
 
     /// <summary>
     /// 將元素入棧
     /// </summary>
     /// <param name="item">目標元素</param>
     public void Push(T item)
     {
         if (length >= this.arr.Length) return;
         this.arr[length] = item;
         this.topElement = this.arr[length];
         this.length = this.length + 1;
     }
 
     /// <summary>
     /// 輸出棧內所有元素
     /// </summary>
     /// <param name="stack">目標棧</param>
     public void Out(Stack<T> stack)
     {
         if (stack.arr.Length <= 0) return;
         foreach (T item in arr)
         {
             Console.WriteLine(item);
         }
         Console.ReadLine();
     }
 }

在泛型中,類名後面的Stack<T>中的T表示它操作的是一個未指定的數據類型,也稱為開放式類型,因為T是一個不明確的類型,在實例化的時候才能知道T的實際類型是什麼,實例化之後也就變成了封閉式類型,如Stack<String>,因為Stack<String>已經明確了T是String類型的。另外一點,Stack<String>和Stack<T>沒有任何繼承上的關系,而是兩種完全獨立的類型。下面來看下泛型的神奇之處吧:

static void Main(string[] args)
{
    Stack<int> std = new Stack<int>(2);
    std.Push(21);
    std.Push("Hello");//編譯不通過,保證了類型安全
    int a = std.Pop();//未發生拆裝箱

    Stack<Good> stdg = new Stack<Good>(2);
    Good sell = new Good("box", 10);
    stdg.Push(sell);
    Good buy = stdg.Pop();//未發生強制類型轉換
    Console.ReadLine();
}

四、總結

以上是泛型的簡單應用,實際應用中,泛型還有很多其他的特性,如泛型的約束、泛型的方法、接口、委托以及繼承等。要理解這些特性,需要把基礎打好,熟練掌握泛型的應用場景,才能事半功倍。

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