泛型概述:
所谓泛型,即通过参数化类型来实现在同一份代码上操作多种数据类型。泛型编程是一种编程范式,它利用“参数化类型”将类型抽象化,从而实现更为灵活的复用。C#泛型赋予了代码更强的类型安全,更好的复用,更高的效率,更清晰的约束(这个特点褒贬不一)。
C#泛型机制简介:
A。C#泛型能力由CLR在运行时支持,区别于C++的编译时模板机制,和Java的编译时“搽拭法”。(C++所有的泛型的处理都在编译时,运行时看不到泛型。Java编译器实际上只是做了类型安全的控制,不能获得高更的效率。)这使得泛型能力可以在各个支持CLR的语言(如VB.NET)之间进行无缝的互操作。
B。C#泛型代码在被编译为IL代码和元数据时,采用特殊的占位符来表示泛型类型,并用专有的IL指令支持泛型操作。而真正的泛型实例化工作以“on-demand”的方式(按需所取,当代码真正碰到实例化的代码时才去实例化。比如一个方法对泛型进行了实例化,但程序没有去调用这个方法,则这个实例化工作就不会发生),发生在JIT编译时。
反编译工具路径(查看IL代码):C:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\Bin\ildasm.exe
C#泛型编译机制:
A。第一轮编译时,编译器只为Stack<T>类型产生“泛型版”的IL代码与元数据--并不进行泛型类型的实例化,T在中间只充当占位符。
B。JIT编译时,当JIT编译器第一次遇到Stack<int>时,将用int替代“泛型版”IL代码与元数据中的T--进行泛型类型的实例化,此时T才成为一个可用的类型。(以后再遇到Stack<int>时会拿内存中存放的上次实例化过的类型,不会再实例化。但如果是新的类型,如Stack<byte>,则会再一次实例化,放到内存中。)
C。CLR为所有类型参数为“引用类型”的泛型类型产生同一份代码;但如果类型参数为“值类型”,对每一个不同的“值类型”,CLR将为其产生一份独立的代码。
C#泛型的几个特点:
A。如果实例化泛型的参数相同,那么JIT编译器会重复使用该类型,因此C#的动态泛型能力避免了C++静态模板可能导致的代码膨胀的问题。
B。C#泛型类型携带有丰富的元数据,因此C#的泛型类型可以应用于强大的反射技术。
C。C#的泛型采用“基类,接口,构造器,值类型/引用类型”的约束方式来实现对类型参数的“显式约束”,提高了类型安全的同时,也丧失了C++模板基于“签名”的隐式约束所具有的高灵活性。
C#泛型类与结构:
class C<U,V>{} //合法
class D:C<string,int>{} //合法(泛型类型可以作为父类被继承)
class E<U,V>:C<U,V>{} //合法(父类C的类型参数使用了子类E的类型参数)
class F<U,V>:C<string,int>{} //合法(父类可以不使用子类的类型参数,直接实例化)
class G:C<U,V>{} //非法(子类是具体类型,父类是泛型类型但没有实例化。当使用G时,不用去实例化,那么C中的U和V是不确定的。所以这样定义是非法的)
C#除可单独声明泛型类型(包括类与结构)外,也可以在基类中包含泛型类型的声明。但基类如果是泛型类,它的类型参数要么已实例化,要么来源于子类(同样是泛型类型)声明的类型参数。
泛型类型的成员:
class C<V>{
public V f1; //声明字段
public D<V> f2; //作为其它泛型类型的参数
public C(V x){
this.f1 = x;
}
}
泛型类型的成员可以使用泛型类型声明中的类型参数。但类型参数如果没有任何约束,则只能在该类型上使用从System.Object继承的公有成员。
泛型接口:
interface IList<T>{
T[] GetElements(); //泛型数组
}
interface IDictionary<K,V>{
void Add(K key, V value);
}
//泛型接口的类型参数要么已实例化,要么来源于实现类声明的类型参数,要么来源于实现类声明的类型参数
class List<T>:IList<T>,IDictionary<int,T>{
public T[] GetElements() { return null; }
public void Add(int index,T value) {}
}
泛型委托:
delegate bool Predicate<T>(T value);
class X{
static bool F(int i) { ... }
static bool G(string s) { ... }
static void Main(){
Predicate<string> p2 = G; //简略写法
Predicate<int> p1 = new Predicate<int>(F);
}
}
泛型委托支持在委托返回值和参数上应用参数类型,这些参数类型同样可以附带合法的约束。
泛型方法:
A。C#泛型机制只支持“在方法声明上包含类型参数”--即泛型方法。
B。C#泛型机制不支持在除在方法外的其它成员(包括属性、事件、索引器、构造器、析构器)的声明上包含类型参数,但这些成员本身可以包含在泛型类型中,并使用泛型类型的类型参数。
C。泛型方法既可以包含在泛型类型中,也可以包含在非泛型类型中。
泛型方法的声明与调用:
public class Finder{
//泛型方法的声明
public static int Find<T> (T[] items,T item){
for(int i=0;i<items.Length;i++){
if(items[i].Equals(item)) { return i; }
}
return -1;
}
}
//泛型方法的调用
int i = Finder.Find<int> (new int[]{1,2,3,4,5},3);
泛型方法的重载:
class MyClass{
void F1<T>(T[] a,int i); //不可以构成重载方法(实例化时没办法区分调用的方法)
void F1<U>(U[] a,int i);
void F2<T>(int x); //可以构成重载方法
void F2(int x);
void F3<T>(T t) where T:A; //不可以构成重载方法
void F3<T>(T t) where T:B;
}
泛型方法的重写:
abstract class Base
{
public abstract T F<T,U>(T t,U u) where U:T;
public abstract T G<T>(T t) where T:IComparable;
}
class Derived:Base{
//合法的重写,约束被默认继承
public override X F<X,Y>(X x,Y y) {}
//非法的重写,指定任何约束都是多余的(不管是重新写一遍父类的约束,又或是想添加新的约束都是不合法的)
public override T G<T>(T t) where T:IComparable {}
}
泛型约束:
A。C#泛型要求对“所有泛型类型或泛型方法的类型参数”的任何假定,都要基于“显示的约束”,以维护C#所要求的类型安全。
B。“显式约束”由where子句表达,可以指定“基类约束”,“接口约束”,“构造器约束”,“值类型/引用类型约束”共四种约束。
C。“显式约束”并非必须,如果没有指定“显式约束”,泛型类型参数将只能访问System.Object类型中的公有方法。
基类约束:
class A { public void F1() { ... } }
class B { public void F2() { ... } }
class C<S,T> where S:A where T:B //S必须继承于A,T必须继承于B
{
//可以在类型为S的变量上调用F1,在类型为T的变量上调用F2
}
接口约束:
interface IPrintable { void Print(); }
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必须实现IComparable,V必须实现IPrintable和IKeyProvider
{
//可以在类型为K的变量上调用CompareTo,在类型为V的变量上调用Print和GetKey
}
构造器约束:
class A { public A(){} }
class B { public B(int i){} }
class C<T> where T:new() //实例类型必须存在无参构造器
{
//可以在其中使用T t = new T();
}
C<A> c = new C<A>(); //可以,A有无参构造器
C<B> c = new C<B>(); //错误,B没有无参构造器(因为自定义了一个构造器后C#不会自动产生一个无参构造器)
值类型/引用类型约束:
public struct A { ... }
public class B { ... }
class C<T> where T:struct
{
//T在这里面是一个值类型
}
C<A> c = new C<A>(); //可以,A是一个值类型
C<B> c = new C<B>(); //错误,B是一个引用类型
其它资料:
泛型的类型安全:
ArrayList al = new ArrayList(); //引入System.Collections
al.Add(1);
al.Add(2);
//al.Add(3.0); //加上这句,编译通过但运行出错
int total = 0;
foreach (int var in al)
{
total += var;
}
Console.WriteLine("Total is {0}", total);
List<int> aLst = new List<int>(); //引入System.Collections.Generics
aLst.Add(1);
aLst.Add(2);
//aLst.Add(3.0); //加上这句,编译通不过
int total2 = 0;
foreach (int var in aLst)
{
total2 += var;
}
Console.WriteLine("Total2 is {0}", total2);
.NET CLR识别泛型:
public class MyList<T> //泛型类
{
private static int objCount = 0;
public MyList() { objCount++; }
public int Count
{
get { return objCount; }
}
}
public class SampleClass
{ }
static void Main(string[] args)
{
MyList<int> myIntList = new MyList<int>();
MyList<int> myIntList2 = new MyList<int>();
MyList<double> myDoubleList = new MyList<double>();
MyList<SampleClass> mySampleList = new MyList<SampleClass>();
Console.WriteLine(myIntList.Count); //2
Console.WriteLine(myIntList2.Count); //2
Console.WriteLine(myDoubleList.Count); //1
Console.WriteLine(mySampleList.Count); //1
Console.WriteLine(new MyList<SampleClass>().Count); //2
}
泛型方法:
public static void Copy<T>(List<T> source, List<T> destination)
{
foreach (T obj in source)
{
destination.Add(obj);
}
}
static void Main(string[] args)
{
List<int> lst1 = new List<int>();
lst1.Add(2);
lst1.Add(4);
List<int> lst2 = new List<int>();
Copy(lst1, lst2);
Console.WriteLine(lst2.Count);
}
泛型的意义何在:
A。泛型可以减少重复的代码,类似的函数,如double和int型的比较函数,可以通过一个泛型方法实现。
B。类型安全和减少装箱、拆箱(指定泛型方法/类的参数类型),并不是泛型的意义,而是泛型带来的两个最明显的好处而已。
C。泛型的真正意义在于:把类型作为参数。它实现了代码之间的很好的横向联系(继承为代码提供了一种从上往下的纵向联系)。