先简单的说一下泛型的作用,假如我们设计一个支持整形的排序函数,然后又需要一个支持浮点类型的排序函数,这样的写两份相似的代码,违反了OOP的原则。利用转型为object,又有封箱拆箱和安全性的问题。所以我们利用泛型,让程序到运行时才去确定到底是要用什么类型。
C#的泛型类似于C++中的模版但是不论在设计上还是功能上都有很多不同。具体可以参考MSDN中的介绍。
泛型的使用
先看一段代码
1: class Program 2: { 3: static void TestMethod(U u1) 4: { 5: Console.WriteLine(u1.ToString()); 6: } 7: static void Main(string[] args) 8: { 9: int number = 10; 10: string str = "String"; 11: TestMethod<int>(number); 12: TestMethod<string>(str); 13: Console.ReadKey(); 14: } 15: }可以看出TestMethod函数设计时并没有设定特定的参数类型,直到运行时才确定具体的类型是int还是string 或者其他类型。
明显可以看出使用泛型大大提高了代码的复用率。
C#泛型的机制
C#泛型能力有CLR在运行时支持
C#泛型代码在编译为IL代码和元数据时,采用特殊的占位符来表示范型类型,并用专有的IL指令支持泛型操作。
而真正的泛型实例化工作以“on-demand”的方式,发生在JIT编译时。 .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }
第一轮编译时,编译器只产生“泛型版”的IL代码与元数据——并不进行泛型的实例化,T在中间只充当占位符。
JIT编译时,当JIT编译器第一次遇到泛型版时,将用特定的类型(如int)替换“范型版”IL代码与元数据中的T——进行泛型类型的实例化。
CLR为所有类型参数为“引用类型”的泛型类型产生同一份代码;但是如果类型参数为“值类型”,对每一个不同的“值类型”,CLR将为其产生一份独立的代码。
因为实例化一个引用类型的泛型,它在内存中分配的大小是一样的,但是当实例化一个值类型的时候,在内存中分配的大小是不一样的。
泛型类与泛型委托
泛型不仅支持方法,还支持类,委托,接口等。
具体的使用于泛型方法类似
1: //泛型类 2: public class Test 3: { 4: private T member; 5: public Test(T member) 6: { 7: this.member = member; 8: } 9: public void Output() 10: { 11: Console.WriteLine(member.ToString()); 12: } 13: } 14: class Program 15: { 16: static void Main(string[] args) 17: { 18: Test<int> t1 = new Test<int>(10); 19: Test<string> t2 = new Test<string>("string"); 20: t1.Output(); 21: t2.Output(); 22: Console.ReadKey(); 23: } 24: } 25: 26: //泛型委托 27: class Program 28: { 29: private delegate void TestDelegate (T output); 30: 31: static void Test (T output) 32: { 33: Console.WriteLine(output.ToString()); 34: } 35: 36: static void Main(string[] args) 37: { 38: TestDelegate<int> d1; 39: d1 = Test<int>; 40: TestDelegate<string> d2; 41: d2 = Test<string>; 42: 43: d1(10); 44: d2("string"); 45: 46: Console.ReadKey(); 47: } 48: }.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }
泛型约束
上面的泛型都是可以支持任何类型,但是我们有时候希望,他只要支持某些类和他的子类。或者说只支持值类型,甚至是两个泛型参数之间有继承关系。
这一些都可以利用where关键字来限定,这些限定我们成为泛型约束。
T:struct 类型参数必须是值类型。
T:class 类型参数必须是引用类型,包括任何类、接口、委托或数组类型。
T:new() 类型参数必须具有无参数的公共构造函数。(当与其他约束一起使用时,new() 约束必须最后指定。)
T: <基类名> 类型参数必须是指定的基类或派生自指定的基类。(不可以于T:class一起使用)
T: <接口名称> 类型参数必须是指定的接口或实现指定的接口。可以指定多个接口约束。约束接口也可以是泛型的。
T:U 为 T 提供的类型参数必须是为 U 提供的参数或派生自为 U 提供的参数。这称为裸类型约束。
1: class TestBase 2: { 3: public int foo; 4: 5: public TestBase() 6: { 7: foo = 10; 8: } 9: 10: public TestBase(int f) 11: { 12: foo = f; 13: } 14: } 15: 16: class Program 17: { 18: static void TestMethod (T t1) where T:TestBase where U:T, new() 19: { 20: U u1 = new U(); 21: Console.WriteLine("{0} + {1}",t1.foo,u1.foo); 22: } 23: static void Main(string[] args) 24: { 25: TestBase t = new TestBase(11); 26: TestMethod (t); 27: Console.ReadKey(); 28: } 29: } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }这些限定让泛型有了很大的规范性,能避免很多问题。
泛型类继承
关于泛型类的继承问题。有一个简单的原则基类如果是泛型类,它的类型要么以实例化,要么来源于子类(同样是泛型类型)声明的类型参数
1: class C { } 2: class D : C<string, int> { } 3: class E : C { } 4: class F : C< string, int> { } 5: //基类如果是泛型类,他的类型参数要么已实例化, 6: //要么来源子类(同样是泛型类型)声明的类型参数。 7: //class G : C { } 非法.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }
关于泛型的多态
由于泛型是运行时才能确定具体的类型,在多态方面有一点点特别,容易产生二义性,如果同时存在确定类型的方法会默认先调用确定类型的方法,而后才寻找可能的泛型方法。
1: public class Test 2: { 3: public void TestMethod(T t1, U u1) 4: { 5: Console.WriteLine("1"); 6: } 7: public void TestMethod(U u1, T t1) 8: { 9: Console.WriteLine("2"); 10: } 11: public void TestMethod(int t, int u) 12: { 13: Console.WriteLine("3"); 14: } 15: } 16: class Program 17: { 18: static void Main(string[] args) 19: { 20: Test<int,int> t1 = new Test<int,int>(); 21: t1.TestMethod(123, 123);//call public void TestMethod(int t, int u) 22: 23: Test<string, int> t2 = new Test<string, int>(); 24: t2.TestMethod("123", 123);//call public void TestMethod(T t1, U u1) 25: 26: //Test t3 = new Test (); 27: //t3.TestMethod("123", "123");//产生二义性 28: 29: Console.ReadKey(); 30: } 31: } 最后给出一堆参考资料http://msdn.microsoft.com/zh-cn/library/sx2bwtw7(v=VS.80).aspx http://msdn.microsoft.com/zh-cn/library/twcad0zb(v=VS.80).aspx http://msdn.microsoft.com/zh-cn/library/sz6zd40f(v=VS.80).aspx http://msdn.microsoft.com/zh-cn/library/0x6a29h6(v=VS.80).aspx http://msdn.microsoft.com/zh-cn/library/b5bx6xee(v=VS.80).aspx http://msdn.microsoft.com/zh-cn/library/d5x73970(v=VS.80).aspx http://msdn.microsoft.com/zh-cn/library/c6cyy67b(v=VS.80).aspx http://msdn.microsoft.com/zh-cn/library/xwth0h0d(v=VS.80).aspx http://www.cnblogs.com/lianyonglove/archive/2007/04/20/720682.html http://dev.csdn.net/htmls/80/80696.html http://www.cnblogs.com/kid-li/archive/2006/11/29/577045.html http://www.blogjava.net/Jack2007/archive/2008/05/05/198566.html