最近给项目组面人,面到一些刚从学校毕业的同学,号称熟练甚至是精通C#。一开始,每每看到简历上写着“精通C#”,都会让人有一种莫名的兴奋。然而,结果往往让人比较失望。经过几次这样的经历后,发现有些简历水分过多,大部分而的确比较熟练,但不是C#,而是C++。或者说,他们“精通”的,是学校所教授的部分,而对于C#与C++不同的部分,就显得十分生疏,甚至只是“听过而已”。
而面试时往往只要问问很简单的问题,就可以看出被面试者对C#的熟练程度了。
例如,要求应试者写一个带泛型的函数,就可以看出对泛型语法的理解。如果应试都对泛型比较熟练,则可以进一步提出,让他写一个带有约束的泛型……
好,切入正题,什么是泛型?泛型的实质是什么?
所谓泛型,是指将类型参数化以达到代码复用提高软件开发工作效率的一种数据类型。<---网上摘来的^_^。
泛型的实质是,将类型作为参数,添加到接口、类或者方法中,以提高接口、类和方法对对不同类型数据的适应性。由于带泛型的这些数据结构扩大了对不同类型的适应、兼容,这些数据结构就可以在更广的范围内进行复用,从而使程序的可复用性得到增加,提高代码的优雅性。
在C#2.0中,泛型被作为一个重要的新语法引入。为了区别类型参与其他参数,语法规定,将类型参数放在法括号对“<>”中,并且可以作为于接口、类和方法之上。这就是说,只要在C++的基础上,添加<>和类型参数,就得到了泛型语法了。例如,在C++中,我们要定义一个类:
class Calculator
{
};
那么,给这个Calculator添加一个类型参数,它就可以适应对不同数据进行操作了。例如:
class Calculator<T> { }
实例化这个类的时候,传入对应的类型,就可以形成一个此类型的Calculator的实例。例如:
Calculator<int> intCalculator = new Calculator<int>();
Calculator<double> doubleCalculator = new Calculator<double>();
我们通过一个泛型类,直接实例化了intCalculator和doubleCalculator两个Calcualtor。这两个实例之所以有差别,是因为我们传入了不同的类型参数:int和double。我们只需要写一个方法,就可以为这两个方法提供不同的方法,例如,比较两个数的大小,我们可以添加一个CompareThem方法:
class Calculator<T> { public int CompareThem(T a, T b)
{ return ((IComparable<T>)a).CompareTo(b); }
}
然后,我们就可以调用它了:
可以看到,当我们调用doubleCalculator的时候,VS提示输入的的参数是具体的double,而不是抽象的T。
对于接口和方法的泛型,使用方法和类的泛型一样,打上尖括号对,添加类型参数,即可。
如此一来,虽然提高了类型的使用范围,但是,也带来了一个负作用,即使用范围太广,任何类型数据都可以往里传,导致C#强类型的特性被过多的削弱了。这样的话,至少会导致两个缺憾:
第一,类型检查被过多削弱,传入的类型会失控。例如,在上例中,我们传进一个没有办法比较的类型来生成实例:
Calculator<Program> programCalculator = new Calculator<Program>();
由于我们对T没有任何限制,Program作为参数被传入,在语法上是允许的。然而传入的结果会导致我们没有办法得到编译时错误。但在,在执行时,由于Program类并没有实现IComparable<T>接口,会导致运行时错误。这是我们要程序设计时不希望看到的。
第二,无法利用传入类型的基类提供的方法。
在上例中,我们的方法要判断两个数是否相等,那么,我们要求传入的参数都要实现IComparable<T>接口,既然T是实现IComparable<T>接口的类,那么,它就应该提供.CompareTo方法,而不需要我们在代码里进行强制的类型转换。
好消息是,这两个缺憾通过“约束”这种语法可以得到较好的解决;坏消息是,我们又要学习一种新的语法了,痛苦的学习曲线啊……^o^
在泛型接口、类、方法的定义后跟where关键字,即可添加对添加的约束。例如,我们约束在上例中的T类型必须实现IComparable<T>接口,我们可以写:
class Calculator<T> where T : IComparable<T> { public int CompareThem(T a, T b)
{ return a.CompareTo(b); }
}
where T : IComparable<T>约定:T必须是实现了IComparable<T>接口的类。对于问题1,此时,我们再定义Calculator<Program>对象,会得到一个编译错误,因为Program没有实现IComparable<T>接口;对于问题2,我们已经可以直接写a.CompareTo(b)了。
另外,顺便提一下,还有一些比较特殊的泛型约束:
where T: new()要求T必须有一个不带参数的构造函数;
where T: struct要求T必须是一个值类型;
where T:class要求T必须是一个引用类型;
多个约束可以同时使用,例如: where T : IComparable<T>, new()要求T类不仅要实现IComparable<T>接口,并且还需要有不带参数的构造函数。
Little knowledge is dangerous.