【基础】泛型的简单理解
前言
最近工作不是很忙,抽出时间来看看C#中基础的东西,也算是“温故而知新”了,于是就看到了泛型这块儿,看了园子里其他园友的文章,讲的都很到位。这篇文章本着简单、容易理解为前提,记录下我自己对泛型的认识,方便以后查看。
泛型是什么
泛型是一种开放式类型,它的出现保证了我们可以创建类型安全的集合。
泛型的应用场景
<1>当一种逻辑适用于多种数据类型时,可以采用泛型,简化代码,提高代码的复用性。
<2>避免不必要的装箱(Boxing)和拆箱(Unboxing)操作。
<3>避免不必要的类型强制转换。
下面通过一个自定义的栈,来对上面的应用场景进行解释。
首先定义一个栈,该栈有一个含参构造函数,有三个方法,元素的入栈Push、元素的出栈Pop、元素的输出Out。代码如下:
1 /// <summary> 2 /// 自定义栈 3 /// </summary> 4 /// <typeparam name="?"></typeparam> 5 class Stack 6 { 7 private int[] arr; 8 private int length = 0; 9 private int topElement; 10 11 /// <summary> 12 /// 设置或获取栈顶元素 13 /// </summary> 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 /// <summary> 28 /// 栈中已存在元素数量 29 /// </summary> 30 public int Length 31 { 32 get { return this.length; } 33 } 34 35 /// <summary> 36 /// 构造函数 37 /// </summary> 38 /// <param name="m"></param> 39 public Stack(int m) 40 { 41 this.arr = new int[m]; 42 } 43 44 /// <summary> 45 /// 将栈顶元素出栈 46 /// </summary> 47 /// <returns></returns> 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 /// <summary> 62 /// 将元素入栈 63 /// </summary> 64 /// <param name="item">目标元素</param> 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 /// <summary> 74 /// 输出栈内所有元素 75 /// </summary> 76 /// <param name="stack">目标栈</param> 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 /// <summary> 2 /// 自定义栈 3 /// </summary> 4 /// <typeparam name="?"></typeparam> 5 class StringStack 6 { 7 private string[] arr; 8 private int length = 0; 9 private string topElement; 10 11 /// <summary> 12 /// 设置或获取栈顶元素 13 /// </summary> 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 /// <summary> 28 /// 栈中已存在元素数量 29 /// </summary> 30 public int Length 31 { 32 get { return this.length; } 33 } 34 35 /// <summary> 36 /// 构造函数 37 /// </summary> 38 /// <param name="m"></param> 39 public StringStack(int m) 40 { 41 this.arr = new string[m]; 42 } 43 44 /// <summary> 45 /// 将栈顶元素出栈 46 /// </summary> 47 /// <returns></returns> 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 /// <summary> 62 /// 将元素入栈 63 /// </summary> 64 /// <param name="item">目标元素</param> 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 /// <summary> 74 /// 输出栈内所有元素 75 /// </summary> 76 /// <param name="stack">目标栈</param> 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 /// <summary> 2 /// 自定义栈 3 /// </summary> 4 /// <typeparam name="?"></typeparam> 5 class ObjectStack 6 { 7 private object[] arr; 8 private int length = 0; 9 private object topElement; 10 11 /// <summary> 12 /// 设置或获取栈顶元素 13 /// </summary> 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 /// <summary> 28 /// 栈中已存在元素数量 29 /// </summary> 30 public int Length 31 { 32 get { return this.length; } 33 } 34 35 /// <summary> 36 /// 构造函数 37 /// </summary> 38 /// <param name="m"></param> 39 public ObjectStack(int m) 40 { 41 this.arr = new object[m]; 42 } 43 44 /// <summary> 45 /// 将栈顶元素出栈 46 /// </summary> 47 /// <returns></returns> 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 /// <summary> 62 /// 将元素入栈 63 /// </summary> 64 /// <param name="item">目标元素</param> 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 /// <summary> 74 /// 输出栈内所有元素 75 /// </summary> 76 /// <param name="stack">目标栈</param> 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 /// <summary> 2 /// 自定义栈 3 /// </summary> 4 /// <typeparam name="T"></typeparam> 5 class Stack<T> where T:IComparable 6 { 7 private T[] arr; 8 private int length = 0; 9 private T topElement; 10 11 /// <summary> 12 /// 设置或获取栈顶元素 13 /// </summary> 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 /// <summary> 28 /// 栈中已存在元素数量 29 /// </summary> 30 public int Length 31 { 32 get { return this.length; } 33 } 34 35 /// <summary> 36 /// 构造函数 37 /// </summary> 38 /// <param name="m"></param> 39 public Stack(int m) 40 { 41 this.arr = new T[m]; 42 } 43 44 /// <summary> 45 /// 将栈顶元素出栈 46 /// </summary> 47 /// <returns></returns> 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 /// <summary> 63 /// 将元素入栈 64 /// </summary> 65 /// <param name="item">目标元素</param> 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 /// <summary> 75 /// 输出栈内所有元素 76 /// </summary> 77 /// <param name="stack">目标栈</param> 78 public void Out(Stack<T> 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是一个不明确的类型,在实例化的时候才能知道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(); }
总结
以上是泛型的简单应用,实际应用中,泛型还有很多其他的特性,如泛型的约束、泛型的方法、接口、委托以及继承等。要理解这些特性,需要把基础打好,熟练掌握泛型的应用场景,才能事半功倍。
—— EOF ——
作者:悠扬的牧笛
地址:https://www.cnblogs.com/xhb-bky-blog/p/5200344.html
声明:本博客原创文字只代表本人工作中在某一时间内总结的观点或结论,与本人所在单位没有直接利益关系。非商业,未授权贴子请以现状保留,转载时必须保留此段声明,且在文章页面明显位置给出原文连接。