啃了啃泛型编程,写点心得,很肤浅,泛型仔细研究起来太复杂,跟反射、委托等等许多东西都有种种关联,慢慢积累吧,今天写点基本的东西。
我们知道C#中有装箱,拆箱的操作,我们使用ArrayList可以知道,当我们向ArrayList中加入数据时,是以Object类型加入的,取出的时候也是以Object类型取出的,因此我们需要对取出的数据进行强制类型转换才能保证使用,代码如下:
ArrayList al=new ArrayList();
al.Add(123);
int temp=(int)al[0];
Console.WriteLine(temp);
|
这样当我们用MSIL反汇编程序看IL代码时会发现,程序进行了装箱,拆箱操作,下图:
可以清楚的看到,画红圈的box及unbox,就是通常所说的装箱,拆箱操作,引我们老师的一句话:“ArrayList的最大特点就是管你怎么操作都不出错。”弊端在哪呢?如果程序规模庞大,如此频繁的装箱拆箱的操作必然导致程序效率低下,因为Object这个类型实在是过于庞大,是处于顶端的一个类型,这是从效率方面考虑,从另一方面安全性的考虑上来说,频繁的装箱操作及拆箱操作也会引发安全问题,因为你随便往里装什么都无所谓,但是如果不知道装的是什么,你就取不出来(或者说取出来不知道怎么用),因此C#2.0引入泛型,提高了程序效率,并引入了新的编程模式,也就是泛型编程,泛型编程不仅可以应用于需要装箱,拆箱操作的数据,编程时恰当的使用泛型思想也可以减化某些工作的实现。并且C#对泛型的支持,是从编译IL代码、元数据到CLR运行时,实例化则是在JIT编译时,与JAVA的泛型相比是有些不同的。
泛型有着更强的类型安全,更好的复用,更高的效率,更清晰的约束。下面开始代码与代码解释。
泛型的基本声明及用法:
代码:
public classStack<T> { private T[] store; private int size; public Stack() { store = new T[10]; size = 0; } public void Push(T x) { store[size++] = x; } public T Pop() { return store[--size]; } } |
一个简单模拟存数据,取数据的泛型类型,代码中<>中的T是这个类的参数类型,写在“<>”中,表示的是这个T是一个
晚绑定的类型,也就是在前两次编译时,并不确定T是一个什么类型,前面说过,泛型的实例化是在JIT编译时,这里我是这么理解的,T可以把它当做一个占位符,未实例化之前,它可能是任意类型,泛型类型可以看做一个抽象类,当实例化的时候,应指定T的参数类型,如下:
Stack<int> stack=newStack<int>(); |
好了,实例化完成后,T的参数类型就明确了,如此一来,有个问题,我听WebCast的时候只听说需要这么写,那不这么写行不行呢?如果我写
编译是不会报错的,而且也没有任何的装箱,拆箱操作,Pop出来的值的Type也完全与Push进去的相符合,这个我需要再研究研究,难道编译器这么智能了。。会自己判断了,汗。无关大局,继续。
class C<V> { public Vf1; publicD<V> f2; public C(Vx) { this.f1=x; } } |
可以看出泛型类型中还是可以写很多东西的,包括其他泛型类型的对象(泛型类型D的对象f2,并且将自己的参数类型V传递给泛型类型D)。
泛型类型继承的语法规则
class C1<U,V>{}//合法 classC2:C1<string,int>{} //合法 class C3<U,V>:C1<U,V>{}//合法 classC4<U,V>:C1<string,int>{} //合法 class C5:C1<U,V>{} //非法 |
这里跟struct类似,基类的参数要么已经实例化,要么来源于子类的参数。如果两者都不是,那运行时将无法实例化子类对象。
泛型类型大体上就简单的说完了,下一个:
泛型接口:
interfaceIList<T> { T[]GetElements(); } interface IDictionary<K,V> { void Add(Kkey,V value); } class MyList<T>:IList<T>,IDictionary<int,T> { public T[]getElements(){} public voidAdd(int index,T value){} } |
注意,接口的参数类型与泛型类型继承中的父类泛型类型类似,当某个类需要实现某个或多个泛型接口时,泛型接口中的参数类型要么已经实例化,要么来源于实现类的参数列表(请注意那个红色的int)。好了,从这个例子可以稍微看出点来,我们可以定义一个可以接收或返回任意数据类型的泛型接口,而实现类只需要根据自身的需要去实现对应数据类型的接口就好了,不仅提高了代码重用,而且提高了效率,当然,这只是泛型接口应用之一。
继续。
泛型委托:
delegate boolpredicate<T>(T value); class X { static void F(int i){} static void G(string s){} static voidmain() { predicate<string>p1=G;
p1(123); predicate<int> p2=new predicate<int>(F);
p2("fuckdawnh"); } }
|
这段代码需要注意的是红色那段的写法,C#2.0以后支持这种写法,但是实际上编译器做的工作是predicate<string>p1=newpredicate<string>(G);所以我老觉得语言越来越难学,本来就因为C#是建立在高层的语言,底层本来就不容易理解,编译器作者又这么为程序员着想,想方设法为我们节省工作量,是好是坏呢?谁知道……额回到正题,泛型这么一引入,得又省了,定义一个返回或需要任意参数类型的参数(注意,委托支持在返回值类型上使用泛型,且参数类型同样可以附带合法的约束),好了,你需要整型的就写int,你需要字符串就写string,也是一种在参数类型上多态的体现吧。。
继续,
泛型方法:
注意泛型方法既可以包含在泛型类型中,也可以包含在非泛型类型中,不包含在属性、事件、索引器、构造器、析构器。
①基本写法:
public class Finder { publicstatic int Find<T>(T[] items,T item) { for(int i=0;i { if(itmes[i].Equals(item)) return i; } return-1; } } int i=Finder.Find<T>(newint[]{1,3,4,5,6,8,9},6);
|
注意Finder类并不是一个泛型类型,而其中的方法Find却是一个返回int类型,需要T类型数组,T类型变量的泛型方法,注意最后调用Find的写法,这是在非泛型类中的泛型方法的典型例子。
继续。
②泛型方法的重载:
class MyClass { voidF1<T>(T[] a,int i){} voidF1<U>(U[] a,int i){} //不构成重载
void F2<T>(int x){}; void F2(intx){} //构成重载
void F3<T>(T t) where T:A{} voidF3<T> where T:B{} //不构成重载 }
|
好了,这里需要注意的地方挺多:
第一个比较好理解,典型的换汤不换药,不构成重载没什么疑问。
第二组则构成重载,虽然T没在F2方法的参数列表中使用,可是T可以做为第一个F2方法内部成员变量的参数类型,并且同样都是方法F2,调用方法是不同的,调用第一个F2的写法(假如T的类型是string)是对象名.F2<string>(123);而调用第二个F2的写法是 对象名.F2(123);是有区别的。
第三组也是无法构成重载的,原因是不到运行时,编译器根本无法判断T是不是类型A,也无法判断T是不是类型B,前面已经说过,不到JIT编译,泛型不会被实例化,因此我觉得要做一个程序员,要有两个层次的认识,一是高层认识,就是你需要知道什么场合、什么时间该用什么方法构造你的代码,二是底层认识,你也需要知道底层大概路线是怎么走的,也需要有内存模型,知道栈堆等等都存什么用的。
继续。
③泛型方法的重写:
abstract class Father { publicabstract T F<T,U>(T t,U u) where U:T; publicabstract T G<T>(T t) where T:IComparable; } class Child:Father
{ publicoverride X F<X,Y>(X x,Y y){} //正确 publicoverride T G<T>(T t) where T:IComparable{} //错误! }
|
注意的一点,当重写泛型方法时,此方法的所有约束都是被默认继承的,因此不需要你手动去添加。
继续下一个大项。
泛型的约束:
对所有泛型类型或泛型方法的类型参数的任何假定,都要基于“显式的约束”,以维护类型安全,泛型约束主要有这么几个:①基类约束,②接口约束,③构造器约束,④值/引用类型约束。如果没有显式约束,则泛型类型只能访问Object里的公有方法。一个个看吧:
①基类约束
class A{public voidF1(){}} class B{public void F2(){}} class C<S,T> where S:A where T:B { static Sa=(S)(new A());
static T b=(T)(new B());
static void Main()
{
a.F1();
b.F2();
} }
|
where后面的S:A就是说这个参数S必须是类型A,参数T必须是类型B,否则将导致运行时异常,当我们对类型C做了基类约束后,那我们便可以通过传递进来的合法参数来调用类型A的F1方法及类型B的F2方法了。注意强制类型转换,这里是将A当成了S的父类,B当成了T的父类。
②接口约束
interface IPrintable{voidPrint();} interface IComparable<T>{int CompareTo(T v)} interface IKeyProvider<T>{T GetKey();} class Dictionary<K,V>
where K:IComparable<K>
where V:IPrintable,IKeyProvider<K> { //可以调用K类型实例的CompareTo方法
//可以调用V类型实例的Print,GetKey方法
}
|
我们可以看到参数K,V被约束为必须实现了某个接口的类型的实例,因此在该类中我们便可使用该实现类中所实现的接口中的方法了,还是要注意约束中接口中的参数,必须已经实例化或来源于实现类。
③构造器约束
class A{publicA(){}} class B{public B(int i){}} class C<T> where T:new() { T t=newT(); } C<A> c=new C<A>(); B<B> c=new C<B>(); //错 |
这里只是注意一下where后面的意思就可以了,这里的whereT:new()的意思是,该类型带有一个无参构造函数,我们看到类型B只有一个带有int类型的构造函数,因此如果将B当做类型传入会发生错误。另外注意,如果需要使用Tt=new T();则必须在约束中指定new();否则无法通过编译。
④值/引用类型约束
public struct A{} public class B{} class C<T> where T:struct { } C<A> c=new C<A>(); C<B> c=new C<B>(); //错误 |
这里也只是需要注意一下where后面约束的意思就可以了,whereT:stuct意思是T是一个值类型,因此第二种写法是错的。
累晕了,写到这泛型的一些基础的东西就说完了,做为C#2.0的核心技术,泛型的应用非常广泛,其复杂程度也是相当之高,光以上这些东西,MSDN里就标出来,这是Level300的,因此还是需要我们多多的积累才能对泛型有一个更全面的认识。
下一篇准备写写委托与事件,这个东西真是不怎么好理解。