【基础】泛型的简单理解


前言

最近工作不是很忙,抽出时间来看看C#中基础的东西,也算是“温故而知新”了,于是就看到了泛型这块儿,看了园子里其他园友的文章,讲的都很到位。这篇文章本着简单、容易理解为前提,记录下我自己对泛型的认识,方便以后查看。

泛型是什么

泛型是一种开放式类型,它的出现保证了我们可以创建类型安全的集合。

泛型的应用场景

<1>当一种逻辑适用于多种数据类型时,可以采用泛型,简化代码,提高代码的复用性。

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

<3>避免不必要的类型强制转换。

下面通过一个自定义的栈,来对上面的应用场景进行解释。

首先定义一个栈,该栈有一个含参构造函数,有三个方法,元素的入栈Push、元素的出栈Pop、元素的输出Out。代码如下:

 1 /// 
 2 /// 自定义栈
 3 /// 
 4 /// 
 5 class Stack
 6 {
 7     private int[] arr;
 8     private int length = 0;
 9     private int topElement;
10 
11     /// 
12     /// 设置或获取栈顶元素
13     /// 
14     public int TopElement
15     {
16         get
17         {
18             this.topElement= arr[length-1];
19             return this.topElement;
20         }
21         set
22         {
23             this.topElement = value;
24         }
25     }
26 
27     /// 
28     /// 栈中已存在元素数量
29     /// 
30     public int Length
31     {
32         get { return this.length; }
33     }
34 
35     /// 
36     /// 构造函数
37     /// 
38     /// 
39     public Stack(int m)
40     {
41         this.arr = new int[m];
42     }
43 
44     /// 
45     /// 将栈顶元素出栈
46     /// 
47     /// 
48     public int Pop()
49     {
50         if (this.length <= 0)
51             return -1;
52         else
53         {
54             int popElement = this.topElement;
55             arr[this.length-1] = 0;
56             this.length = this.length - 1;
57             return popElement;
58         }
59     }
60 
61     /// 
62     /// 将元素入栈
63     /// 
64     /// 目标元素
65     public void Push(int item)
66     {
67         if (length >= this.arr.Length) return;
68         this.arr[length] = item;
69         this.topElement = this.arr[length];
70         this.length = this.length + 1;
71     }
72 
73     /// 
74     /// 输出栈内所有元素
75     /// 
76     /// 目标栈
77     public void Out(Stack stack)
78     {
79         if (stack.arr.Length <= 0) return;
80         foreach (int item in arr)
81         {
82             Console.WriteLine(item);            
83         }
84         Console.ReadLine();
85     }
86 }

以上代码可以正常运行,实现了一个栈的基本功能,但是我想把这个逻辑放在string类型上也通用,怎么办?有人说那好办,把int替换成string就好咯,代码如下:

 1 /// 
 2 /// 自定义栈
 3 /// 
 4 /// 
 5 class StringStack
 6 {
 7     private string[] arr;
 8     private int length = 0;
 9     private string topElement;
10 
11     /// 
12     /// 设置或获取栈顶元素
13     /// 
14     public string TopElement
15     {
16         get
17         {
18             this.topElement = arr[length - 1];
19             return this.topElement;
20         }
21         set
22         {
23             this.topElement = value;
24         }
25     }
26 
27     /// 
28     /// 栈中已存在元素数量
29     /// 
30     public int Length
31     {
32         get { return this.length; }
33     }
34 
35     /// 
36     /// 构造函数
37     /// 
38     /// 
39     public StringStack(int m)
40     {
41         this.arr = new string[m];
42     }
43 
44     /// 
45     /// 将栈顶元素出栈
46     /// 
47     /// 
48     public string Pop()
49     {
50         if (this.length <= 0)
51             return "Empty";
52         else
53         {
54             string popElement = this.topElement;
55             arr[this.length - 1] = string.Empty;
56             this.length = this.length - 1;
57             return popElement;
58         }
59     }
60 
61     /// 
62     /// 将元素入栈
63     /// 
64     /// 目标元素
65     public void Push(string item)
66     {
67         if (length >= this.arr.Length) return;
68         this.arr[length] = item;
69         this.topElement = this.arr[length];
70         this.length = this.length + 1;
71     }
72 
73     /// 
74     /// 输出栈内所有元素
75     /// 
76     /// 目标栈
77     public void Out(StringStack stack)
78     {
79         if (stack.arr.Length <= 0) return;
80         foreach (string item in arr)
81         {
82             Console.WriteLine(item);
83         }
84         Console.ReadLine();
85     }
86 }

经过测试发现,上面的栈也可以正常工作了,但是问题又来了,我想把这个栈的逻辑再应用到long类型上怎么办?聪明的人不会在同一个问题上绊倒三次,既然有那么多类型需要用到这个栈的逻辑,那我索性就给他来个Object,这下总可以了吧~于是Object类型的栈横空出世,代码如下:

 1 /// 
 2 /// 自定义栈
 3 /// 
 4 /// 
 5 class ObjectStack
 6 {
 7     private object[] arr;
 8     private int length = 0;
 9     private object topElement;
10 
11     /// 
12     /// 设置或获取栈顶元素
13     /// 
14     public object TopElement
15     {
16         get
17         {
18             this.topElement = arr[length - 1];
19             return this.topElement;
20         }
21         set
22         {
23             this.topElement = value;
24         }
25     }
26 
27     /// 
28     /// 栈中已存在元素数量
29     /// 
30     public int Length
31     {
32         get { return this.length; }
33     }
34 
35     /// 
36     /// 构造函数
37     /// 
38     /// 
39     public ObjectStack(int m)
40     {
41         this.arr = new object[m];
42     }
43 
44     /// 
45     /// 将栈顶元素出栈
46     /// 
47     /// 
48     public object Pop()
49     {
50         if (this.length <= 0)
51             return null;
52         else
53         {
54             object popElement = this.topElement;
55             arr[this.length - 1] = null;
56             this.length = this.length - 1;
57             return popElement;
58         }
59     }
60 
61     /// 
62     /// 将元素入栈
63     /// 
64     /// 目标元素
65     public void Push(object item)
66     {
67         if (length >= this.arr.Length) return;
68         this.arr[length] = item;
69         this.topElement = this.arr[length];
70         this.length = this.length + 1;
71     }
72 
73     /// 
74     /// 输出栈内所有元素
75     /// 
76     /// 目标栈
77     public void Out(ObjectStack stack)
78     {
79         if (stack.arr.Length <= 0) return;
80         foreach (object item in arr)
81         {
82             Console.WriteLine(item);
83         }
84         Console.ReadLine();
85     }
86 }

经过重构之后,这个栈逻辑可以适用于任何类型,这是毫无疑问的,但是还有一个问题,来看调用的代码:

 1 static void Main(string[] args)
 2 {
 3     ObjectStack ostd = new ObjectStack(3);
 4     ostd.Push(true);
 5     ostd.Push("http://www.cnblogs.com/xhb-bky-blog/");
 6     ostd.Push(12);
 7 
 8     int a = (int)ostd.Pop();
 9     string b = ostd.Pop().ToString();
10     bool c = (bool)ostd.Pop();
11     Console.ReadLine();
12 }
13         

在调用代码中,我们居然看到了装箱和拆箱操作,而装箱操作和拆箱操作是要额外耗费cpu和内存资源的。再来看下面一段代码:

 1         static void Main(string[] args)
 2         {            
 3             ObjectStack ostd = new ObjectStack(3);
 4             Good good1 = new Good("pencil", 5);
 5             Good good2 = new Good("eraser", 2);
 6             Good good3 = new Good("pencilbox", 15);
 7             
 8             ostd.Push(good1);
 9             ostd.Push(good2);
10             ostd.Push(good3);
11 
12             Good mygood = (Good)ostd.Pop();//注意
13             Console.ReadLine();
14         }

上面的代码中发生了类型强制转换,这表明该段代码是不安全的,虽然可以通过编译,但是如果类型对应不上,就会把错误推迟到运行期。为了解决上面的三个问题,泛型应运而生,来看一下泛型是如何解决这些问题的,看代码:

 1 /// 
 2 /// 自定义栈
 3 /// 
 4 /// 
 5 class Stack where T:IComparable
 6 {
 7     private T[] arr;
 8     private int length = 0;
 9     private T topElement;
10 
11     /// 
12     /// 设置或获取栈顶元素
13     /// 
14     public T TopElement
15     {
16         get
17         {
18             this.topElement = arr[length - 1];
19             return this.topElement;
20         }
21         set
22         {
23             this.topElement = value;
24         }
25     }
26 
27     /// 
28     /// 栈中已存在元素数量
29     /// 
30     public int Length
31     {
32         get { return this.length; }
33     }
34 
35     /// 
36     /// 构造函数
37     /// 
38     /// 
39     public Stack(int m)
40     {
41         this.arr = new T[m];
42     }
43 
44     /// 
45     /// 将栈顶元素出栈
46     /// 
47     /// 
48     public T Pop()
49     {
50         //T t = new T();//必须声明new
51         if (this.length <= 0)
52             return default(T);
53         else
54         {
55             T popElement = this.topElement;
56             arr[this.length - 1] = default(T);
57             this.length = this.length - 1;
58             return popElement;
59         }
60     }
61 
62     /// 
63     /// 将元素入栈
64     /// 
65     /// 目标元素
66     public void Push(T item)
67     {
68         if (length >= this.arr.Length) return;
69         this.arr[length] = item;
70         this.topElement = this.arr[length];
71         this.length = this.length + 1;
72     }
73 
74     /// 
75     /// 输出栈内所有元素
76     /// 
77     /// 目标栈
78     public void Out(Stack stack)
79     {
80         if (stack.arr.Length <= 0) return;
81         foreach (T item in arr)
82         {
83             Console.WriteLine(item);
84         }
85         Console.ReadLine();
86     }
87 }

在泛型中,类名后面的Stack中的T表示它操作的是一个未指定的数据类型,也称为开放式类型,因为T是一个不明确的类型,在实例化的时候才能知道T的实际类型是什么,实例化之后也就变成了封闭式类型,如Stack,因为Stack已经明确了T是String类型的。另外一点,Stack和Stack没有任何继承上的关系,而是两种完全独立的类型。下面来看下泛型的神奇之处吧:

static void Main(string[] args)
{
    Stack<int> std = new Stack<int>(2);
    std.Push(21);
    std.Push("Hello");//编译不通过,保证了类型安全
    int a = std.Pop();//未发生拆装箱

    Stack stdg = new Stack(2);
    Good sell = new Good("box", 10);
    stdg.Push(sell);
    Good buy = stdg.Pop();//未发生强制类型转换
    Console.ReadLine();
}

总结

以上是泛型的简单应用,实际应用中,泛型还有很多其他的特性,如泛型的约束、泛型的方法、接口、委托以及继承等。要理解这些特性,需要把基础打好,熟练掌握泛型的应用场景,才能事半功倍。

 作者:悠扬的牧笛

 博客地址:http://www.cnblogs.com/xhb-bky-blog/p/4167108.html

 声明:本博客原创文字只代表本人工作中在某一时间内总结的观点或结论,与本人所在单位没有直接利益关系。非商业,未授权贴子请以现状保留,转载时必须保留此段声明,且在文章页面明显位置给出原文连接。