.net泛型学习
1. 什么是泛型?
泛型是具有占位符(类型参数)的类、结构、接口和方法,这些占位符是类、结构、接口和方法所存储或使用的一个或多个类型的占位符。泛型集合类可以将类型参数用作它所存储的对象的类型的占位符;类型参数作为其字段的类型及其方法的参数类型出现。泛型方法可以将其类型参数用作其返回值的类型或者其某个形参的类型。
泛型把类或方法的类型的确定推迟到实例化该类或方法的时候,也就是说刚开始声明是不指定类型,等到要使用(实例化)时再指定类型;泛型可以用于 类、方法、委托、事件等。
? 举例
public class SortHelper<T> { static void Main(string[] args) { SortHelper<int> sort =new SortHelper<int>(); //将相应的类型加进去即可<>中进行定义T为类型参数 //泛型把类或方法的类型的确定推迟到实例化该类或方法的时候 , //也就是说刚开始声明是不指定类型,等到要使用(实例化)时再指定类型; //泛型可以用于 类、方法、委托、事件等。 //此时进行实例化,定义一个待排序的数组 int[] array = {1, 4, 6, 2, 7, 8}; sort.BubbleSort(array); foreach (var i in array) { Console.WriteLine("{0}",i); } Console.ReadKey(); } //当我们改变类型 的时候需要重写这个类,这个时候应该怎么办呢?我们用占位符来代替,先暂时不声明是什么样的类型 //public void BubbleSort(int[] array),定义一个用于数组排序的方法,此时用占位符进行代替类型参数, //并不指定是什么类型,当真正使用时候再进行实例化 public void BubbleSort(T[] array) { int length = array.Length; for (int i = 0; i < length-2; i++) { for (int j = length-1; j >=1; j--) { if (array[j].CompareTo(array[j-1])<0) { T temp = array[j]; array[j] = array[j-1]; array[j - 1] = temp; } } } } }
2. 泛型方法和泛型类
泛型类与泛型方法的不同在于泛型方法降低了作用的范围,而只是限于当前的方法,作用的范围不再是整个类了。具体体现在声明的强制类型支持与泛型参数约束的位置不同,一个是定义在类的后面,而另一个是定义在方法的后面,实例如下图展示:
? 泛型类的作用范围是整个类
但是此时我们发现整个的类是实现计算的,而如果仅仅因为一个排序的方法把类定义为泛型类实现IComparable,范围显然是太大了,并且根本显得没有必要,故而泛型方法出现了。
/// <summary> /// 泛型类。IComparable是泛型参数约束,必须实现IComparable接口 /// </summary> /// <typeparam name="T">强制类型参数</typeparam> public class SuperCalculator<T>where T:IComparable { //Code:略 public void SpeedSort(T[] array) { //Code:略 } }
? 泛型方法仅仅是作用于这个方法
public class SuperCalculator
{
//Code:略
/// <summary>
/// 泛型的方法,此时是方法实现了IComparable,而不是整个的类了
/// </summary>
/// <typeparam name="T">强制类型参数</typeparam>
/// <param name="array">数组名</param>
public void SpeedSort<T>(T[] array) where T:IComparable
{
//Code:略
}
}
3. 如何运行泛型
? 减少代码的重复率
因为参数类型不定,想要减少代码的重复率,使得程序更加清爽时使用,此时泛型类似于模板,可以根据需要为模板传递参数。也就是我们上面讲的那个例子,将参数类型的确定推到实现的时候再去确定。(第一个例子即是)
? 增强程序的灵活性(泛型参数约束)
当参数的类型不一致但是却要进行比较时,因为标准不一时,运行泛型参数约束即可使得程序的灵活度增强。
比如如果我们一个类,名为book,其属性有标题和价格两个属性,标题是string类型,而价格是int类型,此时我们定义一个数组,包括两本书,此时调用我们之前第一个例子的排序方法,此时我们要注意了,既然是排序,那么免不了比较大小,但是类型不一,比较的标准又在哪里呢?在.NET中实现比较的基本方法是实现IComparable方法,我们先采用非泛型的版本来实现,比较大小的标准是前者大于后者,返回一个大于0的整数,小于返回一个小于0的整数,等于时返回0。
我们以价格为标准来比较
//定义IComparable接口 public interface IComparable { int CompareTo(object obj); } //实现接口,但是我们发现此时调用的price,之后进行的比较,并且还存在一个向下的转化,因为object是基类 //是最上层的,这样转为Book,是向下转化的,违背了Liskov替换原则, public class Book : IComparable { private int price; private string title; public Book(int price, string title) { this.price = price; this.title = title; } public int Price { get { return this.price; } } public string Title { get { return this.title; } } public int CompareTo(object obj) { Book book2 = (Book) obj; return this.Price.CompareTo(book2); } }
这样能够比较,但是排序的sortHelper泛型类并不知道,这样就需要告诉编译器(sortHelper<T>是可以进行比较的),那么就需要其实现接口IComparable。
/// <summary> /// 这是冒泡排序,共执行length-1即可。类中的尖括号中的T表示的是类型,这样可以再传递,因为类型是无法在构造函数中进行传递的 /// </summary> /// 类型参数约束 public class SortHelper<T> where T:IComparable { //当我们改变类型 的时候需要重写这个类,这个时候应该怎么办呢?我们用占位符来代替,先暂时不声明是什么样的类型 //public void BubbleSort(int[] array),定义一个用于数组排序的方法,此时用占位符进行代替类型参数, //并不指定是什么类型,当真正使用时候再进行实例化 public void BubbleSort(T[] array) { int length = array.Length; for (int i = 0; i < length-2; i++) { for (int j = length-1; j >=1; j--) { //此时我们传入的书的属性便是可以进行对比的,能够实现compareto方法 if (array[j].<span style="background-color: rgb(255, 102, 102);">CompareTo</span>(array[j-1])<0) { T temp = array[j]; array[j] = array[j-1]; array[j - 1] = temp; } } } } }
这样实现了使用泛型的排序,可以支持所有的实现IComparable接口类型参数数组,此时程序的比较变得十分的灵活了。
泛型的Where
泛型的Where能够对类型参数作出限定。有以下几种方式。
a) ·where T :struct 限制类型参数T必须继承自System.ValueType。
b) ·where T :class 限制类型参数T必须是引用类型,也就是不能继承自System.ValueType。
c) ·where T :new() 限制类型参数T必须有一个缺省的构造函数
d) ·where T :NameOfClass 限制类型参数T必须继承自某个类或实现某个接口。
以上这些限定可以组合使用,比如:public class Point where T : class, IComparable, new()
? 极大提高集合类型的性能和安全性
性能方面,可以避免隐式的拆箱和装箱问题。
非泛型的时候
const int ListSize = 3; private static void UseArrayList(){ ArrayList list = new ArrayList(); //int i = 100; //list.Add(i); //string value = (string)list[0]; //const int listSize = 3; for (int i = 0; i <ListSize; i++) { list.Add(i); //list的Add的是Object 类型的对象,这里进行的是装箱的操作(将值类型转换到引用类型) } for (int i = 0; i < ListSize; i++) { int value = (int) list[ i ]; //由Object类型(引用类型)强制转换为int(值类型),这是拆箱操作 Console.WriteLine(value); } }
泛型的时候
const int ListSize = 3; private static void UseGenericList() { List<int> list = new List<int>(); //int i = 100; //list.Add(i); //string value = (string) list[0]; //const int listSize = 3; for (int i = 0; i < ListSize; i++) { list.Add(i); //编译时类型已经确定,无需装箱 } for (int i = 0; i < ListSize; i++) { int value = list[i]; //编译的时候类型是确定的,无需拆箱 Console.WriteLine(value); } }
如果我们将其进行时间上的输出,会发现泛型的执行效率是高于集合的。
安全性方面,可以及时的发现编译过程中的错误,避免错误被隐藏。也就是保证了编译时的类型安全。
使用的是非泛型的时候
ArrayList list = new ArrayList(); int i = 100; list.Add(i); //可以通过编译,但是在运行的时候可能会报错,因为此时的编译器无法识别 //list[0]是整型,而是将其视为object类型只有在运行的时候才可能发现这个错误, //此时可能已经将程序交给了用户,使得错误不能被及时发现 string value = (string)list[0];
使用泛型的时候
List<int> list = new List<int>(); int i = 100; list.Add(i); //无法通过编译,因为编译的时候即会识别出其int类型,并且是无法转换为string类型的,从而保证 //了类型的安全 string value = (string)list[0];
强类型支持+泛型参数约束决定了在编译时就可找到不符合参数类型要求的地方,这样就保证了编译时的类型安全,从而避免了运行时的类型转换错误。
4. 泛型的好处
泛型不会强行对值类型进行装箱和拆箱,或对引用类型进行向下强制类型转换,所以性能得到提高。通过知道使用泛型定义的变量的类型限制,编译器可以在一个高得多的程度上验证类型假设,所以泛型提高了程序的类型安全。它允许程序员将一个实际的数据类型的规约延迟至泛型的实例被创建时才确定。
简而言之,通过泛型,可以极大地提高代码的重用度,同时还可以获得强类型的支持,提高了应用程序的性能。
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。