【记】《.net之美》之读书笔记(二) C#中的泛型

前言

上一篇读书笔记,很多小伙伴说这本书很不错,所以趁着国庆假期,继续我的读书之旅,来跟随书中作者一起温习并掌握第二章的内容吧。

一.理解泛型

1.为什么要使用泛型?-----通过使用泛型,可以极大地提高代码的重用度,同时还可以获得强类型的支持,提升了应用程序的性能,避免了隐式的装箱、拆箱,以及运行时的类型转换错误。

2.为什么要有泛型? -----以简单的数组排序为例,第一次我们可能会要求对int型数组进行排序,然后我们很快的写出了答案,

第二次,又要求我们对byte[]数组进行排序,这时候我们可能会思考下,是不是要优化我们的设计思想,但是还不至于修改,我们还是快速的将int[]数组的排序代码进行了复制,将方法签名改为byte[].

第三次,又要求我们对char[]数组进行排序,这个时候我们就不得不停下来思考了,以上两种需求,除了方法签名不一样以为,其他的都一模一样。所以我们引进了泛型的概念,将int[]、byte[]、char[]使用T[]来表示,T代表type,指代任何类型。所以我们又得到了泛型类的概念。

public class SortHelper<T> {
public void BubbleSort(T[] array){
// 方法实现体
}
}

这样,当我们需要对int[]进行排序时,我们可以这样声明:

SortHelper<int> sorter = new SortHelper<int>();
int[] array = { 8, 1, 4, 7, 3 };
sorter.BubbleSort(array);

当我们需要对char[]进行排序时,我们就能这样声明:

SortHelper<char> sorter = new SortHelper<char>();
char [] array = { 8, 1, 4, 7, 3 };
sorter.BubbleSort(array);

再也不用重复的对代码进行复制,然后修改方法签名了。

结论:通过使用泛型,极大的减少了重复代码,使程序更加清爽。泛型类可以让我们在需要时传入任何类型,在.net中,我们称作类型参数。

3.类型参数约束

我们定义了一个Book类,对它实现排序,该怎么实现呢?假设该类包含两个字段,一个int类型的单价price,一个string类型的title标题。

Book[] bookArray = new Book[2];
Book book1 = new Book(30, "HTML5解析");
Book book2 = new Book(21, "JavaScript实战");
bookArray[0] = book1;
bookArray[1] = book2;
SortHelper<Book> sorter = new SortHelper<Book>();
sorter.BubbleSort(bookArray);
foreach (Book b in bookArray) {
Console.WriteLine("Price:{0}", b.Price);
Console.WriteLine("Title:{0}\n", b.Title);
}

那么到底该怎么进行比较大小呢? 是以价格为准呢,还是以书名的字母顺序呢?都不好说,所以这个时候就需要定义一个比较规则了,那么在.net中,实现比较的基本方法是实现IComparable接口。此接口有泛型和非泛型两个。

public class Book :IComparable {
// CODE:上面的实现略
public int CompareTo(object obj) {
Book book2 = (Book)obj;
returnthis.Price.CompareTo(book2.Price);
}
}

这里我们以price为准来进行排序,可以看到CompareTo()方法参数接受了一个object类型的参数,我们却违背了Liskov替换原则。原则要求方法内部不应该对方法所接受的参数进行向下的强制转换。我们都知道,在C#中,object是一切类型的基类。book类继承自Object类,所以我们在将object转换为book进行价格比较时,就称作向下的强制转换.

泛型类,所接受的T类型参数必须能够进行比较--即实现IComparable接口,所以我们可以这样定义:

public class SortHelper<T> where T:IComparable {
// CODE:实现略
}

4.泛型方法

既然有了泛型类,我们就引入了泛型方法的概念,泛型方法的引入主要是为了解决,我们在声明泛型类时,避免因为调用某一个方法不得不将类型参数T传给某个类,使得那些不需要调用该方法的情况下,创建一个类实例也需要传递类型参数的情况。

如下:SpeedSort()就是一个泛型方法

public class SortHelper
{
public void SpeedSort<T>(T[] Array) where T:IComparable
{ }
}

二.泛型和集合类型

1.避免隐式的装箱拆箱

典型的非泛型集合ArrayList,它包容了任何类型, 所以它接受的参数为所有类型的基类Object, 当我们需要获取到一个int[],需要循环遍历ArrayList,并将它一一添加到我们所声明的数组集合中去,由于object是一个引用类型,而int是一个值类型,这样在得到这么一个包含int类型的ArrayList数组时,我们就需要不断的进行装箱、拆箱的操作,这对.net来说是相对耗时的。

通过使用泛型,由于集合中的元素类型在编译时就已确定,避免了装箱和拆箱的操作,很显著的提高集合类型的性能。 在.NET中,与ArrayList作用等同的泛型类型是 List.

2.编译时的类型安全

由于使用泛型,集合中的元素在编译时就已确定,所以当我们想将一个int类型的数值转换为string时,编译器会直接告诉我们编译错误,这种在编译时就检查出的错误,叫做编译时错误,与之相对的,就是我们的运行时错误,即当使用ArrayList时,因为它所接受的参数是object基类,编译器视为将一个int类型的数值转换为string时是正常的,只有运行时才检查出错误。

三.小结

本章学习了为什么需要泛型(避免重复代码)以及如何使用类型参数约束和泛型方法,并且比较了泛型集合List和非泛型集合ArrayList的对比,熟悉并了解了泛型集合的优势和应用。

很开心,又跟随作者学习了第二章泛型的概念,上一篇读书笔记中,很多小伙伴给我留言了,说了很多鼓励的话,很感谢,能和大家一起进步学习,其中有一位小伙伴提到的方法我觉得很棒,就是大量阅读开源源码,确实,我在园子里看到很多对底层原理理解很透彻的大佬们,每每讲解一个知识点时,总是会教我们去跟踪源码,即理解它的底层实现逻辑是啥,这样更能加深我们对知识点的理解和巩固。

我也是在学习中,希望自己在今后的学习和工作中,能掌握这种技巧,帮助自己更好的理解并熟悉它的原理。

上一篇:算法库:基础线性代数子程序库(Basic Linear Algebra Subprograms,BLAS)介绍


下一篇:C++ Templates之模板元编程